นี่คือ Prometeo ซึ่งเป็นเครื่องมือสร้างแบบจำลองเชิงทดลองสำหรับการประมวลผลประสิทธิภาพสูงแบบฝัง prometeo จัดเตรียมภาษาเฉพาะโดเมน (DSL) โดยอิงตามชุดย่อยของภาษา Python ซึ่งช่วยให้สามารถเขียนโปรแกรมคอมพิวเตอร์ทางวิทยาศาสตร์ในภาษาระดับสูง (Python เอง) ได้อย่างสะดวก ซึ่งสามารถแปลงเป็นโค้ด C ที่มีอยู่ในตัวเองประสิทธิภาพสูงได้อย่างง่ายดาย ใช้งานได้บนอุปกรณ์ฝังตัว
สามารถดูเอกสารของ prometeo ได้ที่ Read the Docs ที่ https://prometeo.readthedocs.io/en/latest/index.html
ตัวอย่าง Hello World ง่ายๆ ที่แสดงวิธีรันโปรแกรม prometeo ง่ายๆ จาก Python หรือแปลงเป็น C สร้างและรันได้ที่นี่ ผลลัพธ์จะแสดงผลลัพธ์ของการวิเคราะห์การใช้งานฮีปและเวลาดำเนินการ (ในกรณีนี้จะไม่มีอะไรให้ดูมากนัก :p)
เนื่องจากโปรแกรม prometeo แปลงเป็นโค้ด C บริสุทธิ์ที่เรียกไลบรารีพีชคณิตเชิงเส้นประสิทธิภาพสูง BLASFEO (สิ่งพิมพ์: https://arxiv.org/abs/1704.02457 โค้ด: https://github.com/giaf/blasfeo) เวลาในการดำเนินการจึงสามารถทำได้ เทียบได้กับโค้ดประสิทธิภาพสูงที่เขียนด้วยลายมือ รูปด้านล่างแสดงการเปรียบเทียบเวลา CPU ที่จำเป็นในการดำเนินการแยกตัวประกอบ Riccati โดยใช้โค้ด C ที่เขียนด้วยมือที่ได้รับการปรับให้เหมาะสมสูงพร้อมการเรียกไปยัง BLASFEO และเวลาที่ได้รับด้วยโค้ดที่แปลง prometeo จากตัวอย่างนี้ เวลาการคำนวณที่ได้รับจาก NumPy และ Julia จะถูกเพิ่มเข้ามาเพื่อการเปรียบเทียบด้วย - โปรดสังเกตว่าการใช้งาน Riccati การแยกตัวประกอบสองครั้งล่าสุดนี้ ไม่สามารถฝังได้ง่าย เหมือนกับรหัส C ที่สร้างโดย prometeo และการใช้งาน C ที่เข้ารหัสด้วยมือ การวัดประสิทธิภาพทั้งหมดดำเนินการบน Dell XPS-9360 ที่มาพร้อมกับ CPU i7-7560U ที่ทำงานที่ 2.30 GHz (เพื่อหลีกเลี่ยงความผันผวนของความถี่เนื่องจากการควบคุมปริมาณความร้อน)
นอกจากนี้ prometeo ยังมีประสิทธิภาพเหนือกว่าคอมไพเลอร์ Python ที่ล้ำสมัยอย่าง Nuitka อีกด้วย ตารางด้านล่างแสดงเวลา CPU ที่ได้รับจากเกณฑ์มาตรฐาน Fibonacci
แยกวิเคราะห์ / คอมไพเลอร์ | เวลา CPU [วินาที] |
---|---|
หลาม 3.7 (CPython) | 11.787 |
นุ้ยก้า | 10.039 |
ปิ๊ปปี้ | 1.78 |
โปรเมเทโอ | 0.657 |
prometeo สามารถติดตั้งผ่าน PyPI ด้วย pip install prometeo-dsl
โปรดสังเกตว่า เนื่องจาก prometeo ใช้คำแนะนำประเภทอย่างกว้างขวางเพื่อจัดเตรียมโค้ด Python ด้วยข้อมูลการพิมพ์แบบคงที่ เวอร์ชัน Python ขั้นต่ำที่ต้องการคือ 3.6
หากคุณต้องการติดตั้ง prometeo การสร้างซอร์สบนเครื่องท้องถิ่นของคุณ คุณสามารถดำเนินการดังต่อไปนี้:
git submodule update --init
เพื่อโคลนโมดูลย่อยmake install_shared
จาก <prometeo_root>/prometeo/cpmt
เพื่อคอมไพล์และติดตั้งไลบรารีแบบแบ่งใช้ที่เชื่อมโยงกับแบ็กเอนด์ C โปรดสังเกตว่าพาธการติดตั้งดีฟอลต์คือ <prometeo_root>/prometeo/cpmt/install
virtualenv --python=<path_to_python3.6> <path_to_new_virtualenv>
pip install -e .
จาก <prometeo_root>
เพื่อติดตั้งแพ็คเกจ Python สุดท้าย คุณสามารถเรียกใช้ตัวอย่างใน <root>/examples
ด้วย pmt <example_name>.py --cgen=<True/False>
โดยที่ --cgen
แฟล็กกำหนดว่าโค้ดถูกเรียกใช้งานโดยล่าม Python หรือโค้ด C หรือไม่ สร้างการคอมไพล์และรัน
รหัส Python ( examples/simple_example/simple_example.py
)
from prometeo import *
n : dims = 10
def main () -> int :
A : pmat = pmat ( n , n )
for i in range ( 10 ):
for j in range ( 10 ):
A [ i , j ] = 1.0
B : pmat = pmat ( n , n )
for i in range ( 10 ):
B [ 0 , i ] = 2.0
C : pmat = pmat ( n , n )
C = A * B
pmat_print ( C )
return 0
สามารถรันโดยล่าม Python มาตรฐาน (ต้องใช้เวอร์ชัน > 3.6) และจะดำเนินการดำเนินการพีชคณิตเชิงเส้นที่อธิบายไว้โดยใช้คำสั่ง pmt simple_example.py --cgen=False
ในเวลาเดียวกัน prometeo สามารถแยกวิเคราะห์โค้ดและแผนผังไวยากรณ์นามธรรม (AST) ที่ได้รับการวิเคราะห์เพื่อสร้างโค้ด C ประสิทธิภาพสูงต่อไปนี้:
#include "stdlib.h"
#include "simple_example.h"
void * ___c_pmt_8_heap ;
void * ___c_pmt_64_heap ;
void * ___c_pmt_8_heap_head ;
void * ___c_pmt_64_heap_head ;
#include "prometeo.h"
int main () {
___c_pmt_8_heap = malloc ( 10000 );
___c_pmt_8_heap_head = ___c_pmt_8_heap ;
char * pmem_ptr = ( char * ) ___c_pmt_8_heap ;
align_char_to ( 8 , & pmem_ptr );
___c_pmt_8_heap = pmem_ptr ;
___c_pmt_64_heap = malloc ( 1000000 );
___c_pmt_64_heap_head = ___c_pmt_64_heap ;
pmem_ptr = ( char * ) ___c_pmt_64_heap ;
align_char_to ( 64 , & pmem_ptr );
___c_pmt_64_heap = pmem_ptr ;
void * callee_pmt_8_heap = ___c_pmt_8_heap ;
void * callee_pmt_64_heap = ___c_pmt_64_heap ;
struct pmat * A = c_pmt_create_pmat ( n , n );
for ( int i = 0 ; i < 10 ; i ++ ) {
for ( int j = 0 ; j < 10 ; j ++ ) {
c_pmt_pmat_set_el ( A , i , j , 1.0 );
}
}
struct pmat * B = c_pmt_create_pmat ( n , n );
for ( int i = 0 ; i < 10 ; i ++ ) {
c_pmt_pmat_set_el ( B , 0 , i , 2.0 );
}
struct pmat * C = c_pmt_create_pmat ( n , n );
c_pmt_pmat_fill ( C , 0.0 );
c_pmt_gemm_nn ( A , B , C , C );
c_pmt_pmat_print ( C );
___c_pmt_8_heap = callee_pmt_8_heap ;
___c_pmt_64_heap = callee_pmt_64_heap ;
free ( ___c_pmt_8_heap_head );
free ( ___c_pmt_64_heap_head );
return 0 ;
}
ซึ่งอาศัยแพ็คเกจพีชคณิตเชิงเส้นประสิทธิภาพสูง BLASFEO โค้ดที่สร้างขึ้นจะถูกคอมไพล์และรันทันทีเมื่อรัน pmt simple_example.py --cgen=True
แม้ว่าการแปลโปรแกรมที่เขียนด้วยภาษาอื่นที่มีระดับนามธรรมที่เทียบเคียงกันได้นั้นง่ายกว่าการแปลเป็นโปรแกรมที่มีระดับนามธรรมที่แตกต่างกันมาก (โดยเฉพาะหากภาษาเป้าหมายอยู่ในระดับที่ต่ำกว่ามาก) การแปลโปรแกรม Python เป็นโปรแกรม C ยังคงเกี่ยวข้องกับช่องว่างที่เป็นนามธรรมอย่างมาก ซึ่งไม่ใช่เรื่องง่ายโดยทั่วไป พูดอย่างหลวม ๆ ความท้าทายอยู่ที่ความจำเป็นในการปรับใช้คุณสมบัติที่ได้รับการสนับสนุนโดยภาษาต้นฉบับในภาษาเป้าหมาย โดยเฉพาะอย่างยิ่งเมื่อแปล Python เป็น C ความยากนั้นมาจากระดับนามธรรมของทั้งสองภาษาที่แตกต่างกันและจากข้อเท็จจริงที่ว่าภาษาต้นทางและภาษาเป้าหมายนั้นมีสองประเภทที่แตกต่างกันมาก: Python เป็นแบบ ตีความ , แบบเป็ด และ แบบขยะ -รวบรวม ภาษาและ C เป็นภาษา ที่รวบรวม และ พิมพ์แบบคงที่
งานการแปลง Python เป็น C จะยิ่งท้าทายมากขึ้นหากเราเพิ่มข้อจำกัดที่ว่าโค้ด C ที่สร้างขึ้นจะต้องมีประสิทธิภาพ (แม้สำหรับการคำนวณขนาดเล็กถึงขนาดกลาง) และปรับใช้ได้บนฮาร์ดแวร์แบบฝัง ในความเป็นจริงข้อกำหนดทั้งสองนี้บอกเป็นนัยโดยตรงว่าโค้ดที่สร้างขึ้นไม่สามารถใช้: i) ไลบรารีรันไทม์ที่ซับซ้อน เช่น ไลบรารีรันไทม์ Python ซึ่งโดยทั่วไปไม่พร้อมใช้งานบนฮาร์ดแวร์ฝังตัว ii) การจัดสรรหน่วยความจำแบบไดนามิกที่จะทำให้การดำเนินการช้าและไม่น่าเชื่อถือ (มีข้อยกเว้นสำหรับหน่วยความจำที่ได้รับการจัดสรรในขั้นตอนการตั้งค่าและมีขนาดที่เรียกว่านิรนัย)
เนื่องจากการแปลงโค้ดจากซอร์สสู่ซอร์ส หรือการทรานสปิเลชัน และโดยเฉพาะอย่างยิ่งการแปลงโค้ด Python ไปเป็นโค้ด C ไม่ใช่ขอบเขตที่ยังไม่ได้สำรวจ ต่อไปนี้ เราจึงกล่าวถึงโปรเจ็กต์ที่มีอยู่สองสามโปรเจ็กต์ที่กล่าวถึงเรื่องนี้ ในการทำเช่นนั้น เราจะเน้นย้ำว่าข้อกำหนดเหล่านี้ไม่เป็นไปตามข้อกำหนดข้อใดข้อหนึ่งจากสองข้อที่อธิบายไว้ข้างต้นที่ไหนและอย่างไร กล่าวคือ ประสิทธิภาพ (ขนาดเล็ก) และความสามารถในการฝังได้
มีแพ็คเกจซอฟต์แวร์หลายชุดที่รองรับการแปล Python เป็น C ในรูปแบบต่างๆ
ในบริบทของการประมวลผลประสิทธิภาพสูง Numba เป็นคอมไพเลอร์แบบทันเวลาสำหรับฟังก์ชันตัวเลขที่เขียนด้วย Python ด้วยเหตุนี้ เป้าหมายของมันคือการแปลงฟังก์ชัน Python ที่มีคำอธิบายประกอบอย่างเหมาะสม ไม่ใช่ทั้งโปรแกรม ให้เป็นโค้ด LLVM ประสิทธิภาพสูง เพื่อให้สามารถเร่งการดำเนินการได้ Numba ใช้การแสดงโค้ดภายในที่จะแปลและดำเนินการอนุมานประเภท (อาจเป็นบางส่วน) กับตัวแปรที่เกี่ยวข้องเพื่อสร้างโค้ด LLVM ที่สามารถเรียกจาก Python หรือจาก C/C++ ในบางกรณี กล่าวคือ กรณีที่สามารถดำเนินการอนุมานแบบสมบูรณ์ได้สำเร็จ สามารถสร้างโค้ดที่ไม่ต้องใช้ C API ได้ (โดยใช้แฟล็ก nopython ) อย่างไรก็ตาม รหัส LLVM ที่ปล่อยออกมาจะยังคงพึ่งพา Numpy สำหรับการดำเนินการ BLAS และ LAPACK
Nuitka เป็นคอมไพเลอร์จากต้นทางสู่ต้นทางที่สามารถแปลโครงสร้าง Python ทุกตัวเป็นโค้ด C ที่ลิงก์กับไลบรารี libpython และจึงสามารถแปลโปรแกรม Python คลาสขนาดใหญ่ได้ เพื่อที่จะทำเช่นนั้นได้นั้นขึ้นอยู่กับความจริงที่ว่าหนึ่งในการใช้งานภาษา Python ที่ใช้มากที่สุดคือ CPython นั้นเขียนด้วยภาษา C ที่จริงแล้ว Nuitka สร้างโค้ด C ที่มีการเรียกไปยัง CPython ซึ่งปกติจะดำเนินการโดย ตัวแยกวิเคราะห์ Python แม้จะมีวิธีการ transpilation ที่น่าสนใจและเป็นแบบทั่วไป แต่ก็ไม่สามารถปรับใช้บนฮาร์ดแวร์แบบฝังได้อย่างง่ายดายเนื่องจากการพึ่งพา libpython โดยแท้จริง ในเวลาเดียวกัน เนื่องจากแมปโครงสร้าง Python ค่อนข้างใกล้ชิดกับการใช้งาน CPython จึงสามารถคาดหวังปัญหาด้านประสิทธิภาพหลายประการได้เมื่อพูดถึงการประมวลผลประสิทธิภาพสูงขนาดเล็กถึงขนาดกลาง นี่เป็นเพราะความจริงที่ว่าการดำเนินการที่เกี่ยวข้องกับ เช่น การตรวจสอบประเภท การจัดสรรหน่วยความจำ และการรวบรวมขยะที่อาจทำให้การดำเนินการช้าลงนั้นถูกดำเนินการโดยโปรแกรมที่ถ่ายโอนด้วยเช่นกัน
Cython เป็นภาษาโปรแกรมที่มีเป้าหมายเพื่ออำนวยความสะดวกในการเขียนส่วนขยาย C สำหรับภาษา Python โดยเฉพาะอย่างยิ่ง มันสามารถแปล (เป็นทางเลือก) โค้ดที่พิมพ์เหมือน Python แบบคงที่เป็นโค้ด C ที่อาศัย CPython ในทำนองเดียวกันกับข้อควรพิจารณาสำหรับ Nuitka สิ่งนี้ทำให้มันเป็นเครื่องมือที่ทรงพลังเมื่อใดก็ตามที่เป็นไปได้ที่จะพึ่งพา libpython (และเมื่อค่าใช้จ่ายของมันเล็กน้อย เช่น เมื่อต้องจัดการกับการคำนวณขนาดใหญ่เพียงพอ) แต่ไม่ใช่ในบริบทที่น่าสนใจที่นี่
ท้ายที่สุด แม้ว่าจะไม่ได้ใช้ Python เป็นภาษาต้นฉบับ แต่เราควรพูดถึงว่า Julia เองก็คอมไพล์เป็นโค้ด LLVM แบบทันเวลา (และล่วงหน้าบางส่วน) เช่นกัน อย่างไรก็ตาม โค้ด LLVM ที่ปล่อยออกมานั้นขึ้นอยู่กับไลบรารีรันไทม์ ของ Julia ในลักษณะที่คล้ายกับการพิจารณาที่สร้างขึ้นสำหรับ Cython และ Nuitka
การสับเปลี่ยนโปรแกรมที่เขียนโดยใช้ชุดย่อยที่จำกัดของภาษา Python ไปเป็นโปรแกรม C จะดำเนินการโดยใช้ transpiler ของ prometeo เครื่องมือการแปลงจากต้นทางสู่ต้นทางนี้จะวิเคราะห์แผนผังไวยากรณ์เชิงนามธรรม (AST) ที่เกี่ยวข้องกับไฟล์ต้นทางที่จะทรานสไพล์เพื่อปล่อยโค้ด C ประสิทธิภาพสูงและสามารถฝังได้ ในการทำเช่นนั้น จำเป็นต้องมีกฎพิเศษในโค้ด Python สิ่งนี้ทำให้เป็นงานที่ท้าทายอย่างยิ่งในการแปลภาษาเป็ดระดับสูงที่ตีความแล้วไปเป็นการพิมพ์คงที่ระดับต่ำที่คอมไพล์แล้วที่เป็นไปได้ ในการทำเช่นนั้น เรากำหนดสิ่งที่บางครั้งเรียกว่า DSL แบบฝัง ในแง่ที่ว่าภาษาผลลัพธ์ใช้ไวยากรณ์ของภาษาโฮสต์ (Python เอง) และในกรณีของ prometeo ก็สามารถดำเนินการได้ด้วยล่าม Python มาตรฐาน .
from prometeo import *
nx : dims = 2
nu : dims = 2
nxu : dims = nx + nu
N : dims = 5
def main () -> int :
# number of repetitions for timing
nrep : int = 10000
A : pmat = pmat ( nx , nx )
A [ 0 , 0 ] = 0.8
A [ 0 , 1 ] = 0.1
A [ 1 , 0 ] = 0.3
A [ 1 , 1 ] = 0.8
B : pmat = pmat ( nx , nu )
B [ 0 , 0 ] = 1.0
B [ 1 , 1 ] = 1.0
Q : pmat = pmat ( nx , nx )
Q [ 0 , 0 ] = 1.0
Q [ 1 , 1 ] = 1.0
R : pmat = pmat ( nu , nu )
R [ 0 , 0 ] = 1.0
R [ 1 , 1 ] = 1.0
A : pmat = pmat ( nx , nx )
B : pmat = pmat ( nx , nu )
Q : pmat = pmat ( nx , nx )
R : pmat = pmat ( nu , nu )
RSQ : pmat = pmat ( nxu , nxu )
Lxx : pmat = pmat ( nx , nx )
M : pmat = pmat ( nxu , nxu )
w_nxu_nx : pmat = pmat ( nxu , nx )
BAt : pmat = pmat ( nxu , nx )
BA : pmat = pmat ( nx , nxu )
pmat_hcat ( B , A , BA )
pmat_tran ( BA , BAt )
RSQ [ 0 : nu , 0 : nu ] = R
RSQ [ nu : nu + nx , nu : nu + nx ] = Q
# array-type Riccati factorization
for i in range ( nrep ):
pmt_potrf ( Q , Lxx )
M [ nu : nu + nx , nu : nu + nx ] = Lxx
for i in range ( 1 , N ):
pmt_trmm_rlnn ( Lxx , BAt , w_nxu_nx )
pmt_syrk_ln ( w_nxu_nx , w_nxu_nx , RSQ , M )
pmt_potrf ( M , M )
Lxx [ 0 : nx , 0 : nx ] = M [ nu : nu + nx , nu : nu + nx ]
return 0
ในทำนองเดียวกัน โค้ดด้านบน ( example/riccati/riccati_array.py
) สามารถรันโดยล่าม Python มาตรฐานโดยใช้คำสั่ง pmt riccati_array.py --cgen=False
และ prometeo สามารถสร้าง คอมไพล์ และรันโค้ด C โดยใช้ pmt riccati_array.py --cgen=True
แทน pmt riccati_array.py --cgen=True
เพื่อให้สามารถแปลงเป็น C ได้ จะรองรับเฉพาะชุดย่อยของภาษา Python เท่านั้น อย่างไรก็ตาม คุณลักษณะที่ไม่ใช่แบบ C เช่น ฟังก์ชันโอเวอร์โหลดและคลาสได้รับการสนับสนุนโดย transpiler ของ prometeo ตัวอย่าง Riccati ที่ดัดแปลง ( examples/riccati/riccati_mass_spring_2.py
) ด้านล่างแสดงวิธีการสร้างและใช้งานคลาส
from prometeo import *
nm : dims = 4
nx : dims = 2 * nm
sizes : dimv = [[ 8 , 8 ], [ 8 , 8 ], [ 8 , 8 ], [ 8 , 8 ], [ 8 , 8 ]]
nu : dims = nm
nxu : dims = nx + nu
N : dims = 5
class qp_data :
A : List = plist ( pmat , sizes )
B : List = plist ( pmat , sizes )
Q : List = plist ( pmat , sizes )
R : List = plist ( pmat , sizes )
P : List = plist ( pmat , sizes )
fact : List = plist ( pmat , sizes )
def factorize ( self ) -> None :
M : pmat = pmat ( nxu , nxu )
Mxx : pmat = pmat ( nx , nx )
L : pmat = pmat ( nxu , nxu )
Q : pmat = pmat ( nx , nx )
R : pmat = pmat ( nu , nu )
BA : pmat = pmat ( nx , nxu )
BAtP : pmat = pmat ( nxu , nx )
pmat_copy ( self . Q [ N - 1 ], self . P [ N - 1 ])
pmat_hcat ( self . B [ N - 1 ], self . A [ N - 1 ], BA )
pmat_copy ( self . Q [ N - 1 ], Q )
pmat_copy ( self . R [ N - 1 ], R )
for i in range ( 1 , N ):
pmat_fill ( BAtP , 0.0 )
pmt_gemm_tn ( BA , self . P [ N - i ], BAtP , BAtP )
pmat_fill ( M , 0.0 )
M [ 0 : nu , 0 : nu ] = R
M [ nu : nu + nx , nu : nu + nx ] = Q
pmt_gemm_nn ( BAtP , BA , M , M )
pmat_fill ( L , 0.0 )
pmt_potrf ( M , L )
Mxx [ 0 : nx , 0 : nx ] = L [ nu : nu + nx , nu : nu + nx ]
# pmat_fill(self.P[N-i-1], 0.0)
pmt_gemm_nt ( Mxx , Mxx , self . P [ N - i - 1 ], self . P [ N - i - 1 ])
# pmat_print(self.P[N-i-1])
return
def main () -> int :
A : pmat = pmat ( nx , nx )
Ac11 : pmat = pmat ( nm , nm )
Ac12 : pmat = pmat ( nm , nm )
for i in range ( nm ):
Ac12 [ i , i ] = 1.0
Ac21 : pmat = pmat ( nm , nm )
for i in range ( nm ):
Ac21 [ i , i ] = - 2.0
for i in range ( nm - 1 ):
Ac21 [ i + 1 , i ] = 1.0
Ac21 [ i , i + 1 ] = 1.0
Ac22 : pmat = pmat ( nm , nm )
for i in range ( nm ):
for j in range ( nm ):
A [ i , j ] = Ac11 [ i , j ]
for i in range ( nm ):
for j in range ( nm ):
A [ i , nm + j ] = Ac12 [ i , j ]
for i in range ( nm ):
for j in range ( nm ):
A [ nm + i , j ] = Ac21 [ i , j ]
for i in range ( nm ):
for j in range ( nm ):
A [ nm + i , nm + j ] = Ac22 [ i , j ]
tmp : float = 0.0
for i in range ( nx ):
tmp = A [ i , i ]
tmp = tmp + 1.0
A [ i , i ] = tmp
B : pmat = pmat ( nx , nu )
for i in range ( nu ):
B [ nm + i , i ] = 1.0
Q : pmat = pmat ( nx , nx )
for i in range ( nx ):
Q [ i , i ] = 1.0
R : pmat = pmat ( nu , nu )
for i in range ( nu ):
R [ i , i ] = 1.0
qp : qp_data = qp_data ()
for i in range ( N ):
qp . A [ i ] = A
for i in range ( N ):
qp . B [ i ] = B
for i in range ( N ):
qp . Q [ i ] = Q
for i in range ( N ):
qp . R [ i ] = R
qp . factorize ()
return 0
ข้อจำกัดความรับผิดชอบ: prometeo ยังอยู่ในขั้นตอนเบื้องต้น และมีเพียงการดำเนินการพีชคณิตเชิงเส้นและโครงสร้าง Python เพียงไม่กี่รายการเท่านั้นที่ได้รับการสนับสนุนในขณะนี้