คำถามนี้แม้จะค่อนข้างเก่า แต่ก็ต้องการเกณฑ์มาตรฐานบางประการเนื่องจากคำถามนี้ไม่ใช่วิธีที่ใช้สำนวนง่ายที่สุดหรือเป็นวิธีที่สามารถเขียนได้ในจำนวนบรรทัดน้อยที่สุด แต่เป็นวิธีที่เร็วที่สุด และเป็นเรื่องโง่ที่จะตอบคำถามนั้นโดยไม่มีการทดสอบจริง ดังนั้นฉันจึงเปรียบเทียบวิธีแก้ปัญหาสี่วิธี memset กับ std :: fill เทียบกับ ZERO ของคำตอบของ AnT เทียบกับโซลูชันที่ฉันทำโดยใช้ AVX intrinsics
โปรดทราบว่าโซลูชันนี้ไม่ใช่แบบทั่วไป แต่จะใช้ได้กับข้อมูล 32 หรือ 64 บิตเท่านั้น โปรดแสดงความคิดเห็นหากรหัสนี้ทำอะไรไม่ถูกต้อง
#include<immintrin.h>
#define intrin_ZERO(a,n){\
size_t x = 0;\
const size_t inc = 32 / sizeof(*(a));/*size of 256 bit register over size of variable*/\
for (;x < n-inc;x+=inc)\
_mm256_storeu_ps((float *)((a)+x),_mm256_setzero_ps());\
if(4 == sizeof(*(a))){\
switch(n-x){\
case 3:\
(a)[x] = 0;x++;\
case 2:\
_mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
case 1:\
(a)[x] = 0;\
break;\
case 0:\
break;\
};\
}\
else if(8 == sizeof(*(a))){\
switch(n-x){\
case 7:\
(a)[x] = 0;x++;\
case 6:\
(a)[x] = 0;x++;\
case 5:\
(a)[x] = 0;x++;\
case 4:\
_mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
case 3:\
(a)[x] = 0;x++;\
case 2:\
((long long *)(a))[x] = 0;break;\
case 1:\
(a)[x] = 0;\
break;\
case 0:\
break;\
};\
}\
}
ฉันจะไม่อ้างว่านี่เป็นวิธีที่เร็วที่สุดเนื่องจากฉันไม่ใช่ผู้เชี่ยวชาญด้านการเพิ่มประสิทธิภาพระดับต่ำ แต่เป็นตัวอย่างของการนำไปใช้งานตามสถาปัตยกรรมที่ถูกต้องซึ่งเร็วกว่า memset
ตอนนี้เข้าสู่ผลลัพธ์ ฉันคำนวณประสิทธิภาพสำหรับอาร์เรย์ขนาด 100 int และ long long ทั้งแบบคงที่และแบบไดนามิกที่จัดสรร แต่ยกเว้น msvc ซึ่งกำจัดรหัสตายบนอาร์เรย์แบบคงที่ผลลัพธ์จะเทียบเคียงได้อย่างมากดังนั้นฉันจะแสดงเฉพาะประสิทธิภาพของอาร์เรย์แบบไดนามิก การมาร์กเวลาเป็นมิลลิวินาทีสำหรับการทำซ้ำ 1 ล้านครั้งโดยใช้ฟังก์ชันนาฬิกาที่มีความแม่นยำต่ำของ time.h
เสียงดัง 3.8 (ใช้ฟรอนต์เอนด์ clang, แฟล็กการปรับให้เหมาะสม = / OX / arch: AVX / Oi / Ot)
int:
memset: 99
fill: 97
ZERO: 98
intrin_ZERO: 90
long long:
memset: 285
fill: 286
ZERO: 285
intrin_ZERO: 188
gcc 5.1.0 (แฟล็กการเพิ่มประสิทธิภาพ: -O3 -march = native -mtune = native -mavx):
int:
memset: 268
fill: 268
ZERO: 268
intrin_ZERO: 91
long long:
memset: 402
fill: 399
ZERO: 400
intrin_ZERO: 185
msvc 2015 (แฟล็กการเพิ่มประสิทธิภาพ: / OX / arch: AVX / Oi / Ot):
int
memset: 196
fill: 613
ZERO: 221
intrin_ZERO: 95
long long:
memset: 273
fill: 559
ZERO: 376
intrin_ZERO: 188
มีหลายสิ่งที่น่าสนใจเกิดขึ้นที่นี่: llvm ฆ่า gcc, การเพิ่มประสิทธิภาพที่ไม่แน่นอนโดยทั่วไปของ MSVC (มันกำจัดรหัสตายที่น่าประทับใจบนอาร์เรย์แบบคงที่แล้วมีประสิทธิภาพที่ยอดเยี่ยมสำหรับการเติม) แม้ว่าการใช้งานของฉันจะเร็วขึ้นอย่างมาก แต่อาจเป็นเพราะตระหนักดีว่าการล้างบิตมีค่าใช้จ่ายน้อยกว่าการดำเนินการตั้งค่าอื่น ๆ
การใช้งานของ Clang นั้นมีประโยชน์มากขึ้นเนื่องจากเร็วกว่าอย่างเห็นได้ชัด การทดสอบเพิ่มเติมบางอย่างแสดงให้เห็นว่า memset มีความเชี่ยวชาญเฉพาะสำหรับ memsets ที่ไม่ใช่ศูนย์สำหรับอาร์เรย์ 400 ไบต์นั้นช้ากว่ามาก (~ 220ms) และเทียบได้กับ gcc อย่างไรก็ตาม memset ที่ไม่ใช่ศูนย์ด้วยอาร์เรย์ 800 ไบต์ไม่ทำให้ความเร็วแตกต่างกันซึ่งอาจเป็นเหตุผลว่าทำไมในกรณีนี้ memset จึงมีประสิทธิภาพที่แย่กว่าการใช้งานของฉันความเชี่ยวชาญเฉพาะสำหรับอาร์เรย์ขนาดเล็กและจุดตัดอยู่ที่ประมาณ 800 ไบต์ โปรดทราบว่า gcc 'fill' และ 'ZERO' ไม่ได้ปรับให้เหมาะกับ memset (ดูจากโค้ดที่สร้างขึ้น) gcc เป็นเพียงการสร้างโค้ดที่มีลักษณะการทำงานที่เหมือนกัน
สรุป: memset ไม่ได้รับการปรับให้เหมาะสมกับงานนี้มากนักเช่นเดียวกับผู้คนจะแสร้งทำเป็นว่าเป็น (มิฉะนั้น memset ของ gcc และ msvc และ llvm จะมีประสิทธิภาพเหมือนกัน) หากประสิทธิภาพมีความสำคัญ memset ไม่ควรเป็นทางออกสุดท้ายโดยเฉพาะอย่างยิ่งสำหรับอาร์เรย์ขนาดกลางที่น่าอึดอัดใจเหล่านี้เนื่องจากไม่มีความเชี่ยวชาญในการล้างบิตและไม่ได้รับการปรับให้เหมาะสมด้วยมือที่ดีกว่าที่คอมไพเลอร์สามารถทำได้ด้วยตัวเอง
new
ก็คือ C ++ ...