นี่เป็นอีกเวอร์ชั่นสำหรับเราผู้ใช้ Framework ที่ Microsoft ยกเลิก Array.Clear
เร็วกว่าและเร็วกว่าโซลูชันของ Panos Theof ถึง 4 เท่าและEric J'sและPetar Petrov ขนานหนึ่งถึงสองเท่าเร็วสำหรับอาเรย์ขนาดใหญ่
ก่อนอื่นฉันต้องการให้คุณทราบถึงบรรพบุรุษของฟังก์ชั่นเพราะมันทำให้ง่ายต่อการเข้าใจโค้ด ประสิทธิภาพที่ชาญฉลาดนี่เป็นสิ่งที่ดีมากเมื่อเทียบกับรหัสของ Panos Theof และสำหรับบางสิ่งที่อาจเพียงพอแล้ว:
public static void Fill<T> (T[] array, int count, T value, int threshold = 32)
{
if (threshold <= 0)
throw new ArgumentException("threshold");
int current_size = 0, keep_looping_up_to = Math.Min(count, threshold);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
for (int at_least_half = (count + 1) >> 1; current_size < at_least_half; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
อย่างที่คุณเห็นสิ่งนี้ขึ้นอยู่กับการเพิ่มส่วนที่เริ่มต้นซ้ำแล้วซ้ำอีกสองเท่า สิ่งนี้เรียบง่ายและมีประสิทธิภาพ แต่มันทำงานได้ดีกับสถาปัตยกรรมหน่วยความจำที่ทันสมัย ดังนั้นจึงเกิดรุ่นที่ใช้การเสแสร้งเท่านั้นเพื่อสร้างบล็อกเมล็ดพันธุ์ที่เป็นมิตรกับแคชซึ่งจะถูกทำลายซ้ำแล้วซ้ำอีกในพื้นที่เป้าหมาย:
const int ARRAY_COPY_THRESHOLD = 32; // 16 ... 64 work equally well for all tested constellations
const int L1_CACHE_SIZE = 1 << 15;
public static void Fill<T> (T[] array, int count, T value, int element_size)
{
int current_size = 0, keep_looping_up_to = Math.Min(count, ARRAY_COPY_THRESHOLD);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
int block_size = L1_CACHE_SIZE / element_size / 2;
int keep_doubling_up_to = Math.Min(block_size, count >> 1);
for ( ; current_size < keep_doubling_up_to; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
for (int enough = count - block_size; current_size < enough; current_size += block_size)
Array.Copy(array, 0, array, current_size, block_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
หมายเหตุ: รหัสก่อนหน้านี้จำเป็นต้องใช้(count + 1) >> 1
เป็นขีด จำกัด สำหรับการวนซ้ำสองเท่าเพื่อให้แน่ใจว่าการดำเนินการคัดลอกขั้นสุดท้ายมีอาหารสัตว์เพียงพอที่จะครอบคลุมทุกสิ่งที่เหลืออยู่ นี่จะไม่เป็นกรณีสำหรับการนับคี่ถ้าcount >> 1
จะใช้แทน สำหรับเวอร์ชั่นปัจจุบันไม่มีนัยสำคัญใด ๆ เนื่องจากการวนซ้ำเชิงเส้นจะรับช่วงต่อใด ๆ
ขนาดของเซลล์อาเรย์จะต้องถูกส่งผ่านเป็นพารามิเตอร์เพราะ - แว่นตาใจ - ข้อมูลทั่วไปไม่ได้รับอนุญาตให้ใช้sizeof
นอกเสียจากว่าพวกเขาจะใช้ข้อ จำกัด ( unmanaged
) ที่อาจมีหรือไม่มีในอนาคต การประมาณการที่ไม่ถูกต้องไม่ใช่เรื่องใหญ่ แต่ประสิทธิภาพดีที่สุดถ้าค่านั้นถูกต้องด้วยเหตุผลดังต่อไปนี้:
การประเมินขนาดองค์ประกอบต่ำเกินไปอาจทำให้ขนาดบล็อกเกินกว่าครึ่งหนึ่งของแคช L1 ดังนั้นการเพิ่มโอกาสในการคัดลอกแหล่งข้อมูลที่ได้รับมาจาก L1 และต้องดึงข้อมูลจากระดับแคชที่ช้าลง
การประเมินขนาดองค์ประกอบมากเกินไปส่งผลให้เกิดการใช้งานแคช L1 ของ CPU ไม่เพียงพอหมายความว่าลูปการคัดลอกบล็อกเชิงเส้นจะถูกดำเนินการบ่อยกว่าการใช้งานที่เหมาะสม ดังนั้นค่าโสหุ้ยการวนรอบ / การโทรเพิ่มเติมจึงเกิดขึ้นเกินความจำเป็นอย่างเคร่งครัด
นี่คือมาตรฐานอ้างอิงรหัสของฉันArray.Clear
และอีกสามโซลูชั่นที่กล่าวถึงก่อนหน้านี้ การกำหนดเวลาใช้สำหรับการกรอกอาร์เรย์จำนวนเต็ม ( Int32[]
) ของขนาดที่กำหนด เพื่อลดความผันแปรที่เกิดจากความไม่แน่นอนของแคช ฯลฯ การทดสอบแต่ละครั้งจะถูกดำเนินการสองครั้งกลับไปด้านหลังและกำหนดเวลาสำหรับการดำเนินการครั้งที่สอง
array size Array.Clear Eric J. Panos Theof Petar Petrov Darth Gizka
-------------------------------------------------------------------------------
1000: 0,7 µs 0,2 µs 0,2 µs 6,8 µs 0,2 µs
10000: 8,0 µs 1,4 µs 1,2 µs 7,8 µs 0,9 µs
100000: 72,4 µs 12,4 µs 8,2 µs 33,6 µs 7,5 µs
1000000: 652,9 µs 135,8 µs 101,6 µs 197,7 µs 71,6 µs
10000000: 7182,6 µs 4174,9 µs 5193,3 µs 3691,5 µs 1658,1 µs
100000000: 67142,3 µs 44853,3 µs 51372,5 µs 35195,5 µs 16585,1 µs
หากประสิทธิภาพของรหัสนี้ไม่เพียงพอช่องทางที่สัญญาจะขนานกับลูปการคัดลอกเชิงเส้น (กับเธรดทั้งหมดที่ใช้บล็อกแหล่งที่มาเดียวกัน) หรือ P / Invoke เพื่อนเก่าที่ดีของเรา
หมายเหตุ: การล้างและการเติมบล็อกโดยปกติแล้วจะทำโดยรูทีนย่อยไปยังโค้ดที่มีความเชี่ยวชาญสูงโดยใช้คำสั่ง MMX / SSE และ whatnot ดังนั้นในสภาพแวดล้อมที่เหมาะสมเราจะเรียกคุณธรรมที่เกี่ยวข้องstd::memset
และมั่นใจในประสิทธิภาพระดับมืออาชีพ IOW โดยสิทธิ์ฟังก์ชั่นห้องสมุดArray.Clear
ควรปล่อยเวอร์ชั่นมือรีดของเราในฝุ่น ความจริงที่ว่ามันเป็นวิธีอื่น ๆ รอบ ๆ แสดงให้เห็นว่าไกลจากสิ่งที่ตีจริงๆ กันไปสำหรับการต้องม้วนตัวเองFill<>
ในสถานที่แรกเพราะมันยังคงอยู่ในแกนกลางและมาตรฐาน แต่ไม่ได้อยู่ในกรอบ . NET ได้รับรอบเกือบยี่สิบปีแล้วและเรายังต้อง P / Invoke ซ้ายและขวาสำหรับสิ่งพื้นฐานที่สุดหรือม้วนของเราเอง ...