ฉันใช้อัลกอริทึมใน Swift Beta และสังเกตว่าประสิทธิภาพแย่มาก หลังจากขุดลึกฉันรู้ว่าหนึ่งในคอขวดเป็นสิ่งที่ง่ายเหมือนการเรียงลำดับอาร์เรย์ ส่วนที่เกี่ยวข้องอยู่ที่นี่:
let n = 1000000
var x = [Int](repeating: 0, count: n)
for i in 0..<n {
x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here
ใน C ++ การดำเนินการที่คล้ายกันใช้เวลา0.06 วินาทีบนคอมพิวเตอร์ของฉัน
ใน Python ใช้เวลา0.6 วินาที (ไม่มีลูกเล่นเพียงแค่ y = เรียงลำดับ (x) สำหรับรายการจำนวนเต็ม)
ใน Swift ใช้เวลา6sถ้าฉันรวบรวมมันด้วยคำสั่งต่อไปนี้:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
และมันใช้เวลามากถึง88 ปีถ้าฉันรวบรวมมันด้วยคำสั่งต่อไปนี้:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
การกำหนดเวลาใน Xcode ด้วยบิลด์ "Release" vs. "Debug" จะคล้ายกัน
เกิดอะไรขึ้นที่นี่? ฉันเข้าใจการสูญเสียประสิทธิภาพบ้างเมื่อเปรียบเทียบกับ C ++ แต่ไม่ใช่การชะลอตัว 10 เท่าเมื่อเปรียบเทียบกับ Python บริสุทธิ์
แก้ไข:สภาพอากาศสังเกตว่าการเปลี่ยน-O3
เพื่อให้-Ofast
รหัสนี้ทำงานเกือบเร็วเท่ากับรุ่น C ++! อย่างไรก็ตาม-Ofast
การเปลี่ยนแปลงความหมายของภาษามาก - ในการทดสอบของฉันก็ปิดใช้งานการตรวจสอบสำหรับล้นจำนวนเต็มและล้นอาร์เรย์จัดทำดัชนี ตัวอย่างเช่นด้วย-Ofast
รหัส Swift ต่อไปนี้จะทำงานอย่างเงียบ ๆ โดยไม่หยุดทำงาน (และพิมพ์ขยะบางส่วน):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
ดังนั้น -Ofast
ไม่ใช่สิ่งที่เราต้องการ จุดรวมของสวิฟท์คือเรามีอวนที่ปลอดภัย แน่นอนว่าตาข่ายนิรภัยมีผลกระทบต่อประสิทธิภาพการทำงาน แต่ไม่ควรทำให้โปรแกรมช้าลง 100 เท่า โปรดจำไว้ว่า Java ตรวจสอบขอบเขตของอาเรย์อยู่แล้วและในกรณีทั่วไปการชะลอตัวนั้นเกิดจากปัจจัยที่น้อยกว่า 2 และใน Clang และ GCC เรามี-ftrapv
การตรวจสอบจำนวนเต็ม (เซ็นชื่อ) ล้นและก็ไม่ช้าเช่นกัน
ดังนั้นคำถาม: เราจะได้รับประสิทธิภาพที่เหมาะสมใน Swift ได้อย่างไรโดยไม่สูญเสียตาข่ายนิรภัย?
แก้ไข 2:ฉันทำการเปรียบเทียบมากกว่านี้โดยมีลูปง่าย ๆ ตามแนวของ
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(นี่คือการดำเนินการของ xor ที่นั่นเพื่อให้ฉันสามารถค้นหาลูปที่เกี่ยวข้องได้ง่ายขึ้นในรหัสแอสเซมบลีฉันพยายามเลือกการดำเนินการที่ง่ายต่อการมองเห็น แต่ยัง "ไม่เป็นอันตราย" ในแง่ที่ว่า ถึงจำนวนเต็มล้น)
อีกครั้งมีความแตกต่างกันมากในการทำงานระหว่างและ-O3
-Ofast
ดังนั้นฉันได้ดูรหัสการประกอบ:
ด้วย
-Ofast
สิ่งที่ฉันคาดหวัง ส่วนที่เกี่ยวข้องคือลูปที่มี 5 คำแนะนำภาษาเครื่องเมื่อ
-O3
ฉันได้รับบางสิ่งที่เกินจินตนาการที่สุดของฉัน วงในมีช่วงยาว 88 บรรทัดของรหัสชุดประกอบ ฉันไม่ได้พยายามทำความเข้าใจทั้งหมด แต่ส่วนที่น่าสงสัยที่สุดคือการเรียก 13 รายการของ "callq _swift_retain" และอีก 13 การเรียกร้องของ "callq _swift_release" นั่นคือการเรียกรูทีนย่อย 26 สายในวงด้านใน !
แก้ไข 3:ในความคิดเห็น Ferruccio ขอเกณฑ์มาตรฐานที่ยุติธรรมในแง่ที่ว่าพวกเขาไม่ได้พึ่งพาฟังก์ชั่นในตัว (เช่นการเรียงลำดับ) ฉันคิดว่าโปรแกรมต่อไปนี้เป็นตัวอย่างที่ดีพอสมควร:
let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
for j in 0..<n {
x[i] = x[j]
}
}
ไม่มีเลขคณิตดังนั้นเราจึงไม่ต้องกังวลเกี่ยวกับจำนวนเต็มล้น สิ่งเดียวที่เราทำคือการอ้างอิงอาเรย์มากมาย และผลลัพธ์อยู่ที่นี่ — Swift -O3 สูญเสียปัจจัยเกือบ 500 เมื่อเปรียบเทียบกับ -Ofast:
- C ++ -O3: 0.05 วิ
- C ++ -O0: 0.4 วิ
- Java: 0.2 วิ
- Python กับ PyPy: 0.5 วิ
- Python: 12 วิ
- รวดเร็ว - เร็ว: 0.05 วิ
- Swift -O3: 23 s
- สวิฟท์ -O0: 443 วิ
(หากคุณกังวลว่าคอมไพเลอร์อาจปรับให้เหมาะสมกับลูปที่ไม่มีจุดหมายทั้งหมดคุณสามารถเปลี่ยนเป็นx[i] ^= x[j]
และเพิ่มคำสั่งการพิมพ์ที่ส่งออกx[0]
ซึ่งไม่ได้เปลี่ยนแปลงอะไรเลยการกำหนดเวลาจะคล้ายกันมาก)
และใช่ที่นี่การใช้งาน Python เป็นการใช้งาน Python บริสุทธิ์ที่มีรายชื่อของ ints และซ้อนสำหรับลูป มันควรจะเป็นมากช้ากว่าไม่ได้เพิ่มประสิทธิภาพสวิฟท์ ดูเหมือนว่ามีอะไรบางอย่างแตกหักอย่างรุนแรงด้วย Swift และการทำดัชนีอาร์เรย์
แก้ไข 4:ปัญหาเหล่านี้ (รวมถึงปัญหาด้านประสิทธิภาพอื่น ๆ ) ดูเหมือนจะได้รับการแก้ไขใน Xcode 6 เบต้า 5
สำหรับการเรียงลำดับฉันมีเวลาต่อไปนี้:
- เสียงดังกราว ++ -O3: 0.06 วิ
- swiftc - รวดเร็ว: 0.1 วิ
- swiftc -O: 0.1 วิ
- swiftc: 4 วิ
สำหรับลูปซ้อนกัน:
- เสียงดังกราว ++ -O3: 0.06 วิ
- swiftc - รวดเร็ว: 0.3 วิ
- swiftc -O: 0.4 วิ
- swiftc: 540 วิ
ดูเหมือนว่าไม่มีเหตุผลอีกต่อไปที่จะใช้สิ่งที่ไม่ปลอดภัย-Ofast
(aka -Ounchecked
); ล้วน-O
ผลิตรหัสที่ดีอย่างเท่าเทียมกัน
xcrun --sdk macosx swift -O3
คุณสามารถรวบรวมด้วย: มันสั้นกว่า