วิธีที่เร็วที่สุดในการพิจารณาว่าจำนวนเต็มอยู่ระหว่างสองจำนวนเต็ม (รวม) กับชุดของค่าที่ทราบ


389

มีวิธีเร็วกว่าx >= start && x <= endใน C หรือ C ++ เพื่อทดสอบว่าจำนวนเต็มอยู่ระหว่างสองจำนวนเต็ม?

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

UPDATE : หลังจากที่พยายามตอบรับผมได้มีคำสั่งเพิ่มความเร็วขนาดในหนึ่งบรรทัดของรหัสมากกว่าทำมันปกติx >= start && x <= endทาง

UPDATE : นี่คือโค้ดหลังและก่อนหน้ากับแอสเซมเบลอร์จาก XCode:

วิธีการใหม่

// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)

Ltmp1313:
 ldr    r0, [sp, #176] @ 4-byte Reload
 ldr    r1, [sp, #164] @ 4-byte Reload
 ldr    r0, [r0]
 ldr    r1, [r1]
 sub.w  r0, r9, r0
 cmp    r0, r1
 blo    LBB44_30

วิธีเก่า ๆ

#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)

Ltmp1301:
 ldr    r1, [sp, #172] @ 4-byte Reload
 ldr    r1, [r1]
 cmp    r0, r1
 bls    LBB44_32
 mov    r6, r0
 b      LBB44_33
LBB44_32:
 ldr    r1, [sp, #188] @ 4-byte Reload
 adds   r6, r0, #1
Ltmp1302:
 ldr    r1, [r1]
 cmp    r0, r1
 bhs    LBB44_36

ค่อนข้างน่าทึ่งว่าการลดหรือกำจัดการแตกแขนงนั้นสามารถให้ความเร็วได้อย่างน่าทึ่ง


28
ทำไมคุณถึงกังวลว่าสิ่งนี้ไม่เร็วพอสำหรับคุณ
Matt Ball

90
ใครสนใจว่าทำไมมันเป็นคำถามที่น่าสนใจ มันเป็นเพียงความท้าทายเพื่อความท้าทาย
David พูดว่า Reinstate Monica

46
@SLaks ดังนั้นเราควรเพิกเฉยต่อคำถามเหล่านี้อย่างสุ่มสี่สุ่มห้าและเพียงแค่พูดว่า "ให้เครื่องมือเพิ่มประสิทธิภาพทำมันได้หรือไม่"
เดวิดพูดว่า Reinstate Monica

87
ไม่สำคัญว่าทำไมคำถามนี้จึงถูกถาม มันเป็นคำถามที่ถูกต้องแม้ว่าคำตอบคือไม่
tay10r

41
นี่คือคอขวดในฟังก์ชั่นในหนึ่งในแอพของฉัน
jjxtra

คำตอบ:


527

มีเคล็ดลับเก่า ๆ ในการทำเช่นนี้กับการเปรียบเทียบ / สาขาเดียว ไม่ว่าจะปรับปรุงความเร็วจริง ๆ อาจเปิดให้ถามได้และถึงแม้ว่าจะเป็นเช่นนั้นก็อาจน้อยเกินไปที่จะสังเกตเห็นหรือสนใจ แต่เมื่อคุณเริ่มจากการเปรียบเทียบสองครั้งโอกาสในการปรับปรุงครั้งใหญ่นั้นค่อนข้างไกล รหัสดูเหมือนว่า:

// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
//  upper-lower, simply add + 1 to upper-lower and use the < operator.
    if ((unsigned)(number-lower) <= (upper-lower))
        in_range(number);

ด้วยคอมพิวเตอร์ทั่วไปที่ทันสมัย ​​(เช่นอะไรก็ตามที่ใช้ส่วนประกอบเสริม twos) การแปลงเป็นไม่ได้ลงนามนั้นเป็นสิ่งที่ไม่จริง - เพียงแค่การเปลี่ยนวิธีการดูบิตเดียวกัน

โปรดทราบว่าในกรณีทั่วไปคุณสามารถคำนวณล่วงหน้าupper-lowerนอกลูป (สันนิษฐาน) เพื่อที่จะไม่ได้มีส่วนร่วมในเวลาที่สำคัญ นอกเหนือจากการลดจำนวนคำสั่งสาขาแล้วยังช่วยปรับปรุงการทำนายสาขาด้วย ในกรณีนี้สาขาเดียวกันจะถูกนำมาใช้ไม่ว่าจะเป็นหมายเลขที่อยู่ด้านล่างสุดด้านล่างหรือด้านบนสุดของช่วง

สำหรับวิธีการทำงานแนวคิดพื้นฐานนั้นค่อนข้างง่าย: จำนวนลบเมื่อมองว่าเป็นตัวเลขที่ไม่ได้ลงชื่อจะมีขนาดใหญ่กว่าสิ่งใด ๆ ที่เริ่มเป็นตัวเลขบวก

ในทางปฏิบัติวิธีการนี้แปลnumberและช่วงเวลาไปยังจุดต้นทางและตรวจสอบว่าnumberอยู่ในช่วงเวลา[0, D]หรือD = upper - lowerไม่ ถ้าnumberด้านล่างลดผูกพัน: เชิงลบและถ้าข้างต้นที่ถูกผูกไว้บน: มีขนาดใหญ่กว่าD


8
@ TomásBadan: พวกเขาทั้งสองจะเป็นหนึ่งรอบในเครื่องจักรที่สมเหตุสมผล สิ่งที่มีราคาแพงคือสาขา
Oliver Charlesworth

3
การแยกสาขาเพิ่มเติมเกิดขึ้นเนื่องจากการลัดวงจรหรือไม่ หากเป็นกรณีนี้lower <= x & x <= upper(แทนlower <= x && x <= upper) จะให้ประสิทธิภาพที่ดีขึ้นด้วยหรือไม่
Markus Mayr

6
@ AK4749, jxh: เจ๋งเหมือนนักเก็ตตัวนี้ฉันลังเลที่จะ upvote เพราะไม่มีอะไรน่าเสียดายที่จะแนะนำว่านี่คือการฝึกฝนที่เร็วขึ้น (จนกว่าจะมีคนทำการเปรียบเทียบแอสเซมเบลอร์และข้อมูลการทำโปรไฟล์) สำหรับทุกสิ่งที่เรารู้ว่าคอมไพเลอร์ของ OP อาจทำให้รหัส OP ที่มีสาขา opcode เดียว ...
โอลิเวอร์ Charlesworth

152
ว้าว!!! สิ่งนี้ส่งผลให้ลำดับการปรับปรุงในแอปของฉันมีขนาดใหญ่ขึ้นสำหรับรหัสบรรทัดนี้โดยเฉพาะ โดยการคำนวณล่วงหน้าต่ำกว่าการทำโปรไฟล์ของฉันลดลงจากเวลา 25% ของฟังก์ชันนี้เป็นน้อยกว่า 2%! คอขวดในขณะนี้คือบวกและการลบการดำเนินงาน แต่ฉันคิดว่ามันอาจจะดีพอตอนนี้ :)
jjxtra

28
อ่าตอนนี้ @PsychoDad ได้อัปเดตคำถามแล้วมันชัดเจนว่าทำไมเรื่องนี้ถึงเร็วขึ้น จริงรหัสมีผลข้างเคียงในการเปรียบเทียบซึ่งเป็นเหตุผลที่คอมไพเลอร์ไม่สามารถเพิ่มประสิทธิภาพการลัดวงจรออกไป
Oliver Charlesworth

17

มันยากที่จะสามารถทำการปรับแต่งที่สำคัญให้กับโค้ดในระดับเล็ก ๆ ประสิทธิภาพที่เพิ่มขึ้นอย่างมากมาจากการสังเกตและแก้ไขโค้ดจากระดับที่สูงขึ้น คุณอาจสามารถขจัดความจำเป็นในการทดสอบช่วงทั้งหมดหรือทำเพียง O (n) ของพวกเขาแทน O (n ^ 2) คุณอาจสามารถจัดลำดับการทดสอบอีกครั้งเพื่อให้ด้านหนึ่งของความไม่เท่าเทียมกันนั้นส่อให้เห็นอยู่เสมอ แม้ว่าอัลกอริธึมจะเหมาะสม แต่กำไรก็มีแนวโน้มที่จะมาเมื่อคุณเห็นว่ารหัสนี้ทดสอบช่วง 10 ล้านครั้งอย่างไรและคุณหาวิธีที่จะทำการผสมมันและใช้ SSE เพื่อทำการทดสอบหลายแบบพร้อมกัน


16
แม้ downvotes ที่ฉันยืนอยู่กับคำตอบของฉัน: แอสเซมบลีที่สร้างขึ้น (ดูลิงค์ pastebin ในความคิดเห็นต่อคำตอบที่ยอมรับ) เป็นสิ่งที่แย่มากสำหรับบางอย่างในวงด้านในของฟังก์ชั่นการประมวลผลพิกเซล คำตอบที่ได้รับการยอมรับนั้นเป็นกลอุบายที่ประณีต แต่เอฟเฟ็กต์ที่น่าทึ่งเกินกว่าที่จะคาดเดาได้ในการกำจัดเศษเสี้ยวของส่วนต่อการทำซ้ำ ผลรองบางอย่างมีอำนาจเหนือและฉันยังคงคาดหวังว่าความพยายามในการเพิ่มประสิทธิภาพกระบวนการทั้งหมดในการทดสอบครั้งนี้จะทำให้ได้รับการเปรียบเทียบช่วงที่ชาญฉลาดในฝุ่น
Ben Jackson

17

ขึ้นอยู่กับจำนวนครั้งที่คุณต้องการทำการทดสอบผ่านข้อมูลเดียวกัน

หากคุณทำการทดสอบในครั้งเดียวอาจไม่มีวิธีที่มีความหมายในการเร่งความเร็วอัลกอริทึม

หากคุณกำลังทำสิ่งนี้เพื่อชุดค่าที่ จำกัด มากคุณสามารถสร้างตารางการค้นหาได้ การจัดทำดัชนีอาจมีราคาแพงกว่า แต่ถ้าคุณสามารถใส่ทั้งตารางในแคชได้คุณสามารถลบการแยกทั้งหมดออกจากโค้ดซึ่งควรทำให้เร็วขึ้น

สำหรับข้อมูลของคุณตารางการค้นหาจะเป็น 128 ^ 3 = 2,097,152 หากคุณสามารถควบคุมหนึ่งในสามของตัวแปรเพื่อให้คุณพิจารณาอินสแตนซ์ทั้งหมดที่start = Nในครั้งเดียวขนาดของชุดการทำงานจะลดลงเป็น128^2 = 16432ไบต์ซึ่งน่าจะพอดีกับแคชที่ทันสมัยที่สุด

คุณยังคงต้องทำการเปรียบเทียบโค้ดจริงเพื่อดูว่าตารางการค้นหาแบบไม่มีสาขานั้นเร็วกว่าการเปรียบเทียบที่ชัดเจนหรือไม่


ดังนั้นคุณจะจัดเก็บการค้นหาบางอย่างที่ให้ค่าเริ่มต้นและสิ้นสุดและมันจะมี BOOL บอกคุณว่ามันอยู่ในระหว่าง?
jjxtra

แก้ไข. มันจะเป็นตารางการค้นหา bool between[start][end][x]3D: หากคุณรู้ว่ารูปแบบการเข้าถึงของคุณจะเป็นอย่างไร (เช่น x กำลังเพิ่มขึ้นแบบซ้ำซาก) คุณสามารถออกแบบตารางเพื่อรักษาตำแหน่งแม้ว่าพื้นที่ทั้งตารางจะไม่พอดีกับหน่วยความจำ
Andrew Prock

ฉันจะดูว่าฉันสามารถไปลองวิธีนี้และดูว่ามันจะไปไหม ฉันวางแผนที่จะทำมันด้วยเวกเตอร์บิตต่อบรรทัดที่บิตจะถูกตั้งค่าถ้าจุดนั้นอยู่ในวงกลม คิดว่าจะเร็วกว่าไบต์หรือ int32 เทียบกับการปิดบังบิต?
jjxtra

2

คำตอบนี้คือการรายงานการทดสอบที่ทำกับคำตอบที่ยอมรับได้ ฉันทำการทดสอบช่วงปิดบนเวกเตอร์ขนาดใหญ่ของจำนวนเต็มแบบสุ่มที่จัดเรียงและทำให้ฉันประหลาดใจวิธีพื้นฐานของ (ต่ำ <= num && num <= สูง) เป็นจริงเร็วกว่าคำตอบที่ยอมรับข้างต้น! ทำการทดสอบบน HP Pavilion g6 (AMD A6-3400APU พร้อม RAM 6GB นี่คือรหัสหลักที่ใช้สำหรับการทดสอบ:

int num = rand();  // num to compare in consecutive ranges.
chrono::time_point<chrono::system_clock> start, end;
auto start = chrono::system_clock::now();

int inBetween1{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (randVec[i - 1] <= num && num <= randVec[i])
        ++inBetween1;
}
auto end = chrono::system_clock::now();
chrono::duration<double> elapsed_s1 = end - start;

เปรียบเทียบกับต่อไปนี้ซึ่งเป็นคำตอบที่ยอมรับด้านบน:

int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
        ++inBetween2;
}

ให้ความสนใจว่า randVec เป็นเวกเตอร์ที่เรียงลำดับ สำหรับ MaxNum ทุกขนาดวิธีแรกจะชนะอันดับที่สองบนเครื่องของฉัน!


1
ข้อมูลของฉันไม่ได้จัดเรียงและการทดสอบของฉันอยู่บน CPU แขน iPhone ผลลัพธ์ของคุณที่มีข้อมูลและ CPU แตกต่างกันอาจแตกต่างกัน
jjxtra

เรียงในการทดสอบของฉันเป็นเพียงเพื่อให้แน่ใจว่าขีด จำกัด บนไม่น้อยกว่าขีด จำกัด ล่าง
rezeli

1
ตัวเลขที่จัดเรียงหมายถึงการคาดคะเนสาขาจะเชื่อถือได้มากและทำให้ทุกสาขาถูกต้องยกเว้นเพียงเล็กน้อยที่จุดเปลี่ยนแปลง ข้อได้เปรียบของโค้ดไร้สาขาคือมันจะกำจัดการสะกดผิดเหล่านี้ในข้อมูลที่ไม่สามารถคาดเดาได้
Andreas Klebinger

0

สำหรับการตรวจสอบช่วงตัวแปรใด ๆ :

if (x >= minx && x <= maxx) ...

มันเร็วกว่าที่จะใช้การทำงานของบิต:

if ( ((x - minx) | (maxx - x)) >= 0) ...

สิ่งนี้จะลดสองกิ่งเป็นหนึ่ง

หากคุณสนใจเกี่ยวกับประเภทที่ปลอดภัย:

if ((int32_t)(((uint32_t)x - (uint32_t)minx) | ((uint32_t)maxx - (uint32_t)x)) > = 0) ...

คุณสามารถรวมการตรวจสอบช่วงตัวแปรเพิ่มเติมเข้าด้วยกัน:

if (( (x - minx) | (maxx - x) | (y - miny) | (maxy - y) ) >= 0) ...

สิ่งนี้จะลด 4 สาขาเป็น 1

มันเป็น3.4 เท่าเร็วกว่าเดิมใน GCC:

ป้อนคำอธิบายรูปภาพที่นี่


-4

เป็นไปไม่ได้หรือที่จะทำการบิตบิทในจำนวนเต็ม?

เนื่องจากต้องอยู่ระหว่าง 0 ถึง 128 หากตั้งค่าบิตที่ 8 (2 ^ 7) เป็น 128 หรือมากกว่า กรณีขอบจะเจ็บปวด แต่เนื่องจากคุณต้องการเปรียบเทียบรวม


3
เขาต้องการที่จะทราบว่าที่x <= end ไม่end <= 128 x <= 128
Ben Voigt

1
ข้อความนี้ " เนื่องจากต้องอยู่ระหว่าง 0 ถึง 128 หากตั้งค่าบิตที่ 8 (2 ^ 7) เป็น 128 หรือมากกว่า " ผิด พิจารณา 256.
Happy Green Kid Naps

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