ประสิทธิภาพของ ArcGISScripting และชุดข้อมูลเชิงพื้นที่ขนาดใหญ่


38

ขณะนี้ฉันกำลังเขียนสคริปต์ไพ ธ อนโดยใช้โมดูล arcgisscripting เพื่อประมวลผลชุดข้อมูลที่มีขนาดใหญ่พอสมควร (รวม 10,000 เร็กคอร์ดโดยรวม) ทำให้เป็นมาตรฐานในตารางจำนวนเล็กน้อยทั้งหมด 8 รายการ กระบวนการนี้ประกอบด้วยการสร้างฟีเจอร์ตามพิกัดของ tuples (x, y) และการสร้างกราฟ (โหนดและเส้น) โดยใช้ความสัมพันธ์ในตารางอีก 7 ตารางเพื่อเป็นแนวทาง ผลลัพธ์สุดท้ายคือฐานข้อมูลส่วนบุคคลทางภูมิศาสตร์ (pgdb / fgdb) ที่มีโหนดและขอบชุดข้อมูลเชิงพื้นที่ที่แสดงภาพความสัมพันธ์

ความพยายามครั้งแรกของฉันคือการใช้คิวรีของตารางฐานข้อมูลใหม่และชุดระเบียน SearchCursor เพื่อเติมตารางลิงก์ (InsertCursor) สำหรับความสัมพันธ์แบบกลุ่มต่อกลุ่มที่เกิดขึ้นมากมาย สิ่งนี้ทำงานได้ดีมากยกเว้นเวลาประมวลผล 15-20 นาที

การใช้โมดูล cProfiler ใน Python เป็นที่ชัดเจนว่า 'thrashing' เป็นฐานข้อมูลส่วนบุคคลเมื่อดำเนินการค้นหาเพื่อเติมตารางลิงก์พร้อมกับการร้องขอเคอร์เซอร์ (ค้นหาและแทรกเคอร์เซอร์) ทำให้เกิดประสิทธิภาพที่น่าตกใจ

ด้วยการปรับเปลี่ยนเล็กน้อยฉันได้รับเวลาประมวลผลต่ำกว่า 2.5 นาที การแลกเปลี่ยนเป็นโครงสร้างบางส่วนของคีมาฐานข้อมูลในรหัสและการ จำกัด การร้องขอสำหรับเคอร์เซอร์ arcgisscripting เพื่อ InsertCursors เมื่อความสัมพันธ์ทั้งหมดจะถูกตรวจสอบ

คำถามของฉันคือหนึ่งในการแสดง

  • ผู้คนใช้เทคนิคใดในการรักษาเวลาคำนวณที่สมเหตุสมผลเมื่อทำงานกับชุดข้อมูลขนาดใหญ่
  • มีวิธีแนะนำ ESRI ใดบ้างที่ฉันพลาดในการค้นหาการปรับให้เหมาะสม

    ฉันเข้าใจค่าใช้จ่ายที่เกิดขึ้นเมื่อสร้างเคอร์เซอร์ arcgisscripting โดยเฉพาะถ้ามาจากฐานข้อมูลส่วนบุคคลแม้ว่าหลังจากการค้นหาคำตอบที่เกี่ยวข้องกับประสิทธิภาพการทำงานที่ยาวนานจากเว็บไซต์นี้และ Google ฉันรู้สึกว่าประสิทธิภาพไม่ได้อยู่ในระดับแนวหน้าของความพยายามของประชาชน .

  • ในฐานะผู้ใช้ผลิตภัณฑ์ ESRI มีใครคาดหวังและเอาผิดประสิทธิภาพการทำงานล่าช้าเหล่านี้หรือไม่

UPDATE

หลังจากทำงานกับผลิตภัณฑ์นี้ฉันได้รวบรวมรายการเทคนิคการปรับให้เหมาะสมซึ่งได้ดำเนินการแปลงข้อมูลเชิงพื้นที่จากรูปแบบที่เหมาะสมไปเป็นฐานข้อมูลภูมิศาสตร์ สิ่งนี้ได้รับการพัฒนาขึ้นสำหรับส่วนบุคคลและฐานข้อมูลไฟล์ tidbits:

  1. อ่านข้อมูลและหาเหตุผลเข้าข้างตนเองในหน่วยความจำ นี้จะลดเวลาของคุณครึ่ง

  2. สร้างคลาสคุณลักษณะและตารางในหน่วยความจำ ใช้ชุดข้อมูลคุณลักษณะชุดข้อมูล 'in_memory' เพื่อใช้หน่วยความจำของคุณเป็นดิสก์ RAM ทำหน้าที่ของคุณแล้วเขียนลงดิสก์

  3. หากต้องการเขียนลงดิสก์ให้ใช้ CopyFeatureclass สำหรับคลาสคุณลักษณะและ CopyRow สำหรับตาราง

3 สิ่งเหล่านี้มีสคริปต์ที่แปลงคุณสมบัติมากกว่า 100,000 รายการเป็นฐานข้อมูลทางภูมิศาสตร์จาก 30 นาทีถึง 30 - 40 วินาทีซึ่งรวมถึงคลาสความสัมพันธ์ด้วย พวกเขาจะไม่ถูกนำมาใช้เบา ๆ วิธีการข้างต้นส่วนใหญ่ใช้หน่วยความจำมากซึ่งอาจทำให้คุณมีปัญหาหากคุณไม่ใส่ใจ


1
คุณเคยลองใช้รูปแบบการจัดเก็บอื่นหรือไม่? ฐานข้อมูลไฟล์ทำงานอย่างไร
Derek Swingley

ไฟล์ฐานข้อมูลทางภูมิศาสตร์นั้นทำงานได้แย่กว่าฐานข้อมูลส่วนบุคคลเล็กน้อย ฉันใช้เวลาเมื่อวานนี้ในการตั้งค่าและปรับแต่งอินสแตนซ์ ArcSDE เพื่อทดสอบประสิทธิภาพในรูปแบบองค์กร ฉันจะให้คุณโพสต์ในการค้นพบของฉัน
OptimizePrime

2
สิ่งนี้ไม่ได้ช่วยคุณในตอนนี้ แต่ประสิทธิภาพเคอร์เซอร์ 10.1 ใน Python ได้รับการปรับปรุงโดยปัจจัยขนาดใหญ่ (บางสิ่งในช่วงของขนาดโดยเฉลี่ยต่อกรณี) ด้วยโมดูลการเข้าถึงข้อมูลใหม่
Jason Scheirer

ใช้ In_memory นั้น InMemoryWorkspace edndoc.esri.com/arcobjects/9.2/ComponentHelp/esriDataSourcesGDB/...ซึ่งหลังจากจำนวนข้อของแถวทิ้งทุกอย่างไปยัง ScratchWorkspaceFactory (เช่น FileGDB) และอาศัย FileGDB ที่จะทำทุกอย่าง
Ragi Yaser Burhum

คำตอบ:


56

แม้ว่าคำถามนี้จะได้รับคำตอบแล้ว แต่ฉันคิดว่าฉันสามารถพูดได้สองเซ็นต์

การปฏิเสธความรับผิด : ฉันทำงานให้กับ ESRI ที่ทีม GeoDatabase เป็นเวลาหลายปีและรับผิดชอบในการบำรุงรักษาส่วนต่าง ๆ ของรหัส GeoDatabase (การกำหนดเวอร์ชัน, เคอร์เซอร์, EditSessions, ประวัติศาสตร์, คลาสความสัมพันธ์ ฯลฯ )

ฉันคิดว่าแหล่งที่มาที่ใหญ่ที่สุดของปัญหาประสิทธิภาพการทำงานกับรหัส ESRI ไม่เข้าใจความหมายของการใช้วัตถุต่าง ๆ โดยเฉพาะอย่างยิ่งรายละเอียด "เล็กน้อย" ของ abstractions GeoDatabase ต่างๆ! บ่อยครั้งที่การสนทนาเปลี่ยนไปใช้ภาษาที่เป็นตัวการสำคัญของปัญหาด้านประสิทธิภาพ ในบางกรณีก็สามารถ แต่ไม่ใช่ตลอดเวลา มาเริ่มกันที่การสนทนาภาษากันก่อน

1.- ภาษาการเขียนโปรแกรมที่คุณเลือกมีความสำคัญเฉพาะเมื่อคุณกำลังทำสิ่งที่ซับซ้อนในวงแคบ ส่วนใหญ่แล้วนี่ไม่ใช่กรณี

ช้างขนาดใหญ่ในห้องคือที่หลักของทุกรหัส ESRI ที่คุณมี ArcObjects - และArcObjects ถูกเขียนใน C ++ ใช้ COM มีค่าใช้จ่ายในการสื่อสารกับรหัสนี้ สิ่งนี้เป็นจริงสำหรับ C #, VB.NET, python หรืออะไรก็ตามที่คุณใช้

คุณชำระราคาเมื่อเริ่มต้นรหัสนั้น นั่นอาจเป็นค่าใช้จ่ายเล็กน้อยหากคุณทำเพียงครั้งเดียว

จากนั้นคุณจ่ายราคาทุกครั้งที่คุณโต้ตอบกับ ArcObjects

โดยส่วนตัวฉันมักจะเขียนรหัสสำหรับลูกค้าของฉันใน C # เพราะง่ายและเร็วพอ อย่างไรก็ตามทุกครั้งที่ฉันต้องการย้ายข้อมูลไปรอบ ๆ หรือทำการประมวลผลสำหรับข้อมูลจำนวนมากที่ถูกนำไปใช้ในการประมวลผลทางภูมิศาสตร์แล้วฉันเพิ่งเริ่มต้นระบบย่อยการเขียนสคริปต์และส่งผ่านพารามิเตอร์ของฉัน ทำไม?

  • มันถูกนำไปใช้แล้ว เหตุใดจึงต้องคิดค้นล้อใหม่
  • มันจริงอาจจะเร็วขึ้น "เร็วกว่าเขียนใน C #?" ใช่ ถ้าฉันใช้พูดโหลดข้อมูลด้วยตนเองก็หมายความว่าฉันจ่ายราคาของ. NET การสลับบริบทในวงแน่น ทุก GetValue, แทรก, ShapeCopy มีค่าใช้จ่าย ถ้าฉันโทรหนึ่งครั้งใน GP กระบวนการโหลดข้อมูลทั้งหมดนั้นจะเกิดขึ้นในการนำ GP ไปใช้จริงในสภาพแวดล้อม COM ฉันไม่จ่ายราคาสำหรับการสลับบริบทเพราะไม่มี - และด้วยเหตุนี้มันจึงเร็วกว่า

อ๋อใช่แล้วถ้าจะใช้ฟังก์ชั่นการประมวลผลทางภูมิศาสตร์ ที่จริงแล้วคุณต้องระวัง

2. GP เป็นกล่องดำที่คัดลอกข้อมูล (อาจไม่จำเป็น)

มันเป็นดาบสองคม มันเป็นกล่องดำที่ใช้เวทมนตร์บางอย่างภายในและแยกผลลัพธ์ออกมา แต่ผลลัพธ์เหล่านั้นซ้ำกันบ่อยมาก 100,000 แถวสามารถแปลงเป็น 1,000,000 แถวบนดิสก์ได้อย่างง่ายดายหลังจากคุณเรียกใช้ข้อมูลของคุณผ่าน 9 ฟังก์ชันที่แตกต่างกัน การใช้ฟังก์ชั่น GP เพียงอย่างเดียวก็เหมือนกับการสร้างโมเดล GP เชิงเส้นและ ...

3. การกำหนดฟังก์ชั่น GP มากเกินไปสำหรับชุดข้อมูลขนาดใหญ่นั้นไม่มีประสิทธิภาพสูง GP Model คือ (อาจ) เทียบเท่ากับการดำเนินการสืบค้นด้วยวิธีที่โง่จริงๆ

ตอนนี้อย่าเข้าใจฉันผิด ฉันชอบโมเดล GP - มันช่วยฉันจากการเขียนรหัสอยู่ตลอดเวลา แต่ฉันก็ทราบด้วยว่ามันไม่ใช่วิธีที่มีประสิทธิภาพที่สุดในการประมวลผลชุดข้อมูลขนาดใหญ่

คุณเคยได้ยินQuery Plannerบ้างไหม? มันเป็นงานที่จะดูคำสั่ง SQL ที่คุณต้องการที่จะดำเนินการสร้างแผนการดำเนินการในรูปแบบของกราฟกำกับที่มีลักษณะเหมือน heck มากเช่นแบบจำลอง GP , ดูสถิติที่เก็บไว้ในฐานข้อมูลและเลือกมากที่สุด การสั่งซื้อที่เหมาะสมที่จะดำเนินการให้ GP เพียงรันพวกเขาในการที่คุณนำสิ่งเพราะมันไม่ได้มีสถิติที่จะทำอะไรอย่างชาญฉลาดมากขึ้น - คุณจะวางแผนแบบสอบถาม และคาดเดาอะไร ลำดับที่คุณดำเนินการสิ่งต่าง ๆ นั้นขึ้นอยู่กับชุดข้อมูลของคุณ ลำดับที่คุณดำเนินการสิ่งต่าง ๆ สามารถสร้างความแตกต่างระหว่างวันและวินาทีและขึ้นอยู่กับคุณในการตัดสินใจ

คุณพูดว่า "เยี่ยมมาก" ฉันจะไม่เขียนบทด้วยตัวเองและระมัดระวังเกี่ยวกับวิธีเขียนสิ่งต่างๆ แต่คุณเข้าใจ abstractions GeoDatabase หรือไม่

4. ไม่เข้าใจ abstractions GeoDatabase สามารถกัดคุณได้อย่างง่ายดาย

แทนที่จะชี้ให้เห็นทุกสิ่งที่เป็นไปได้ที่จะทำให้คุณมีปัญหาให้ฉันแค่ชี้ให้เห็นข้อผิดพลาดทั่วไปบางอย่างที่ฉันเห็นตลอดเวลาและคำแนะนำบางอย่าง

  • เข้าใจความแตกต่างระหว่างถูก / ผิดสำหรับเคอร์เซอร์รีไซเคิ่ล การตั้งค่าสถานะเล็กน้อยเล็ก ๆ นี้เป็นจริงสามารถทำให้คำสั่งรันไทม์ของขนาดเร็วขึ้น
  • วางตารางของคุณในLoadOnlyModeสำหรับโหลดข้อมูล ทำไมต้องอัพเดตดัชนีในทุกส่วนแทรก?
  • เข้าใจว่าถึงแม้ว่า IWorkspaceEdit :: StartEditing จะมีลักษณะเหมือนกันในพื้นที่ทำงานทั้งหมด แต่เป็นสัตว์ร้ายที่แตกต่างกันมากในทุกแหล่งข้อมูล ใน Enterprise GDB คุณอาจมีเวอร์ชันหรือรองรับธุรกรรม ในรูปร่างไฟล์นั้นจะต้องมีการใช้งานในวิธีที่แตกต่างกันมาก คุณจะใช้ Undo / Redo อย่างไร คุณจำเป็นต้องเปิดใช้งานหรือไม่ (ใช่มันสามารถสร้างความแตกต่างในการใช้หน่วยความจำได้)
  • ความแตกต่างระหว่างการดำเนินการแบทช์หรือการดำเนินงานแถวเดียว กรณีตรงประเด็นGetRow vs GetRows - นี่คือความแตกต่างระหว่างการทำแบบสอบถามเพื่อรับหนึ่งแถวหรือทำหนึ่งแบบสอบถามเพื่อดึงข้อมูลหลายแถว การวนลูปอย่างแน่นหนาพร้อมการเรียกใช้ GetRow หมายถึงประสิทธิภาพที่น่ากลัวและเป็นสาเหตุของปัญหาด้านประสิทธิภาพอันดับที่ 1
  • ใช้UpdateSearchedRows
  • เข้าใจความแตกต่างระหว่างCreateRowและCreateRowBuffer ความแตกต่างอย่างมากในการแทรกไทม์
  • ทำความเข้าใจกับIRow :: Storeและ IFeature :: Store ทำให้เกิดการดำเนินการpolymorphic ที่หนักหน่วงเป็นพิเศษ นี่อาจเป็นเหตุผล # 2 ผู้ร้ายประสิทธิภาพช้า มันไม่เพียงบันทึกแถวนี่เป็นวิธีที่ทำให้แน่ใจว่าเครือข่ายทางเรขาคณิตของคุณเป็นปกติที่ตัวแก้ไข ArcMap จะได้รับแจ้งว่าแถวมีการเปลี่ยนแปลงซึ่งจะแจ้งให้ทราบถึงคลาสความสัมพันธ์ทั้งหมดที่มีส่วนเกี่ยวข้องกับแถวนี้ แน่ใจว่า cardinality นั้นถูกต้อง ฯลฯ คุณไม่ควรแทรกแถวใหม่ด้วยสิ่งนี้คุณควรใช้InsertCursor !
  • คุณต้องการ (จำเป็น) ทำเม็ดมีดเหล่านั้นใน EditSession หรือไม่? มันสร้างความแตกต่างอย่างมากถ้าคุณทำหรือไม่ การดำเนินการบางอย่างจำเป็นต้องใช้ (และทำให้สิ่งต่าง ๆ ช้าลง) แต่เมื่อคุณไม่ต้องการให้ข้ามคุณสมบัติการเลิกทำ / ทำซ้ำ
  • เคอร์เซอร์เป็นทรัพยากรที่มีราคาแพง เมื่อคุณมีหมายเลขอ้างอิงถึงหนึ่งคุณจะรับประกันได้ว่าคุณจะมีความสอดคล้องและความโดดเดี่ยวและมีค่าใช้จ่าย
  • แคชทรัพยากรอื่น ๆ เช่นการเชื่อมต่อฐานข้อมูล (ไม่ต้องสร้างและทำลายการอ้างอิงเวิร์กสเปซของคุณ) และตัวจัดการตาราง (ทุกครั้งที่คุณเปิดหรือปิดหนึ่งตาราง - เมตาดาต้าตารางจำนวนมากต้องอ่าน)
  • การวาง FeatureClasses ภายในหรือภายนอก FeatureDataset สร้างความแตกต่างอย่างมากในประสิทธิภาพ มันไม่ได้หมายถึงเป็นคุณลักษณะขององค์กร!

5. และสุดท้ายและไม่น้อย ...

ทำความเข้าใจกับความแตกต่างระหว่างI / O bound และการทำงานของ CPU bound

ฉันคิดอย่างจริงใจเกี่ยวกับการเพิ่มมากขึ้นในแต่ละรายการเหล่านั้นและอาจทำรายการบล็อกที่ครอบคลุมทุกหัวข้อเหล่านั้น แต่รายการในมือของปฏิทินของฉันเพิ่งตบหน้าและเริ่มตะโกนใส่ฉัน

สองเซ็นต์ของฉัน


5
ขอบคุณ ฉันควรทำงานแทนการเขียนบทความนี้ฮ่าฮ่า
Ragi Yaser Burhum

3
+1 ขอบคุณมากสำหรับข้อมูลของคุณ Mr Burhum นี่คือประเภทของการตอบสนองที่ฉันตั้งใจจะได้รับ ถ้าฉันสามารถโหวตได้สองครั้งฉันก็ทำได้ !! สิ่งที่ผู้ใช้ ArcGISScripting (หลาม) ควรใช้จากคำตอบนี้คือแม้ว่าลิงก์จะแสดงถึงแนวคิดของ ArcObjects และ. Net แต่วัตถุ COM พื้นฐานนั้นเหมือนกันการเข้าใจวัตถุเหล่านี้จะช่วยให้คุณวางแผนรหัสได้ดีขึ้นในภาษาใด ๆ ข้อมูลที่ดีมากมายที่นี่ !!
OptimizePrime

1
@OptimizePrime นั่นเป็นบทสรุปที่ยอดเยี่ยม และคุณพูดถูก - คุณไม่สามารถเพิกเฉยต่อผลกระทบของ ArcObjects หากคุณต้องการบีบประสิทธิภาพออกจากผลิตภัณฑ์ ESRI
Ragi Yaser Burhum

1
ขอบคุณฉันแทนที่ store () โดยใส่เคอร์เซอร์และประหยัดเวลาในแอปพลิเคชันของฉัน!
superrache

5

โดยทั่วไปสำหรับการคำนวณประสิทธิภาพฉันพยายามหลีกเลี่ยงการใช้สิ่งที่เกี่ยวข้องกับ ESRI สำหรับตัวอย่างของคุณฉันขอแนะนำให้ทำขั้นตอนเป็นขั้นตอนแรกอ่านข้อมูลลงในออบเจ็กต์หลามปกติทำการคำนวณจากนั้นขั้นตอนสุดท้ายจะแปลงเป็นรูปแบบเชิงพื้นที่ ESRI ขั้นสุดท้าย สำหรับเร็กคอร์ด ~ 10k คุณอาจหลีกเลี่ยงการเก็บทุกอย่างไว้ในหน่วยความจำเพื่อการประมวลผลซึ่งจะทำให้ได้รับประสิทธิภาพที่ชัดเจนขึ้น


ขอขอบคุณสำหรับการตอบสนองของคุณ. มันเป็นข้อเสนอแนะที่ดี ฉันเริ่ม refactor รหัสเพื่อดำเนินการตามขั้นตอนที่จำเป็นก่อนที่จะใช้ arcgisscripting หลังจากทำงานกับซอฟต์แวร์มาตั้งแต่สมัย ArcInfo ฉันพบว่ามันน่าผิดหวังที่ประสิทธิภาพของ CPU และความสามารถด้านฮาร์ดแวร์เพิ่มขึ้นประสิทธิภาพของ ArcGIS Map / Info / Editor XX นั้นคงที่ บางทีการแนะนำของ GPU อาจเพิ่มสิ่งต่าง ๆ แม้ว่า refactor ที่ดีของฐานรหัส ESRI อาจช่วยได้เช่นกัน
OptimizePrime

1

คุณอาจพิจารณาตัวเลือกที่แสดงในตัวอย่าง ESRI Geocoder ทั้งนี้ขึ้นอยู่กับฮาร์ดแวร์ที่คุณมี มันช่วยให้คุณมีกรอบการแบ่งชุดข้อมูลขนาดใหญ่และเรียกใช้อินสแตนซ์ของหลามหลายอินสแตนซ์เพื่อให้คุณมีวิธีการแบบมัลติเธรดเกือบ ฉันเห็นประสิทธิภาพการเข้ารหัสทางภูมิศาสตร์เพิ่มขึ้นจาก 180,000 ต่อชั่วโมงในอินสแตนซ์ Python เดียวไปกว่าหนึ่งล้านด้วยการหมุน 8 กระบวนการแบบขนานบนเครื่องของฉัน

ฉันได้เห็นว่าการออกไปให้ไกลที่สุดเท่าที่จะทำได้และรักษาข้อมูลไว้ในฐานข้อมูลและทำงานได้ในตารางของฉันและเพียงแค่ใช้สิ่งที่ GIS ชัดเจนในอาณาจักร ESRI ให้ประสิทธิภาพที่สำคัญเพิ่มขึ้น


นี่เป็นแนวคิดที่ยอดเยี่ยม ฉันมีโอกาสที่จะเธรดกระบวนการบางอย่างในสคริปต์นี้ แต่ฉันพบว่าคอขวดของฉันกำลังเริ่มต้นไลบรารี COM และ Geodatabase I / O เกี่ยวกับ I / O ฉันได้ลดความจำเป็นในการเขียนครั้งเดียว ถ้าฉันใช้เวลาในการปรับให้เหมาะสมที่สุดเจ้านายของฉันจะฟิต;) ดังนั้นฉันจึงปล่อยเกลียวเป็นขั้นตอนสุดท้ายของการแสดงถ้าเขาขออะไรเพิ่มเติม ในขณะนี้ฉันกำลังประมวลผลฟีเจอร์ 60,000 รายการต่อนาที
OptimizePrime

0

คุณอาจพบว่ากระทู้อื่น ๆ ในฟอรัมน่าสนใจเนื่องจากอยู่ในบริบทการปรับให้เหมาะสม แต่สำหรับข้อมูลแรสเตอร์และโดยรวม:

รวบรวม Python Scripts ที่ใช้ ArcGIS Geoprocessing Tools?

เวลาในการประมวลผลโดยใช้เครื่องมือกล่องเครื่องมือ ArcGIS Hydrology ในสคริปต์ Python แบบสแตนด์อะโลนเทียบกับ ArcCatalog?

การตั้งค่า gp.scratchworkspace สร้างความแตกต่างอย่างมากสำหรับฉันในรหัสไพ ธ อนบางตัวที่ฉันเขียนเพื่อทำการวาดภาพสันปันน้ำ

คุณสามารถโพสต์ตัวอย่างโค้ดที่แสดงตัวเลข 1 และ 2 ใน UPDATE ของคุณกับคำถามเดิมได้หรือไม่? ฉันสนใจที่จะดูกลไกของมัน (แม้ว่าฉันจะสมมติว่าคุณกำลังจัดการกับข้อมูลคลาสคุณลักษณะเท่านั้นที่นี่)

ขอบคุณทอม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.