<เร็วกว่า <= หรือไม่


1574

คือเร็วกว่าif( a < 901 )if( a <= 900 )

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


153
ฉันเห็นเหตุผลว่าทำไมคำถามนี้ควรจะปิด (และโดยเฉพาะอย่างยิ่งไม่ถูกลบเป็นคะแนนเสียงกำลังแสดง) ให้ความสำคัญทางประวัติศาสตร์ของคุณภาพของคำตอบและความจริงที่ว่าคำถามชั้นนำอื่น ๆ ในการปฏิบัติงานยังคงเปิด อย่างมากควรถูกล็อค ยิ่งไปกว่านั้นหากคำถามนั้นถูกเข้าใจผิด / ไร้เดียงสาความจริงที่ปรากฏในหนังสือหมายความว่าข้อมูลที่ผิดดั้งเดิมมีอยู่ในแหล่ง "น่าเชื่อถือ" ที่ใดที่หนึ่งและคำถามนี้จึงสร้างสรรค์ขึ้นเพื่อช่วยให้ชัดเจนขึ้น
Jason C

32
คุณไม่เคยบอกเราว่าคุณกำลังอ้างอิงถึงหนังสือเล่มใด
Jonathon Reinhart

159
พิมพ์ดีดเป็นครั้งที่สองได้เร็วกว่าการพิมพ์< <=
Deqing

6
เป็นจริงเมื่อวันที่ 8086
Joshua

7
จำนวน upvotes อย่างชัดเจนแสดงให้เห็นว่ามีคนหลายร้อยคนที่ overoptimize อย่างมาก
m93a

คำตอบ:


1704

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

  • testหรือcmpการเรียนการสอนซึ่งชุดEFLAGS
  • และJccคำสั่ง (กระโดด)ขึ้นอยู่กับประเภทการเปรียบเทียบ (และเลย์เอาต์โค้ด):
    • jne - กระโดดถ้าไม่เท่ากับ -> ZF = 0
    • jz - กระโดดถ้าศูนย์ (เท่ากับ) -> ZF = 1
    • jg - กระโดดถ้ายิ่งใหญ่กว่า -> ZF = 0 and SF = OF
    • ( ฯลฯ ... )

ตัวอย่าง (แก้ไขเพื่อความกะทัดรัด) รวบรวมด้วย$ gcc -m32 -S -masm=intel test.c

    if (a < b) {
        // Do something 1
    }

รวบรวมเพื่อ:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jge     .L2                          ; jump if a is >= b
    ; Do something 1
.L2:

และ

    if (a <= b) {
        // Do something 2
    }

รวบรวมเพื่อ:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jg      .L5                          ; jump if a is > b
    ; Do something 2
.L5:

ดังนั้นข้อแตกต่างระหว่างสองอย่างนี้ก็คือjgเมื่อเทียบกับjgeคำสั่ง ทั้งสองจะใช้เวลาเท่ากัน


ฉันต้องการพูดถึงความคิดเห็นที่ไม่มีอะไรบ่งบอกว่าคำแนะนำการกระโดดที่แตกต่างกันใช้เวลาเท่ากัน อันนี้ค่อนข้างยากที่จะตอบ แต่นี่คือสิ่งที่ฉันสามารถให้ได้: ในการอ้างอิงชุดคำสั่ง Intelพวกเขาทั้งหมดจะถูกจัดกลุ่มเข้าด้วยกันภายใต้คำสั่งทั่วไปเดียวJcc(กระโดดถ้าตรงตามเงื่อนไข) การจัดกลุ่มเดียวกันทำร่วมกันภายใต้คู่มืออ้างอิงการปรับให้เหมาะสมในภาคผนวก C. เวลาแฝงและปริมาณงาน

Latency - จำนวนรอบสัญญาณนาฬิกาที่จำเป็นสำหรับแกนประมวลผลเพื่อให้การประมวลผลของμopsทั้งหมดเป็นรูปแบบคำสั่งเสร็จสมบูรณ์

ปริมาณงาน - จำนวนรอบสัญญาณนาฬิกาที่จำเป็นต้องรอก่อนที่พอร์ตปัญหาจะมีอิสระที่จะยอมรับคำสั่งเดิมอีกครั้ง สำหรับคำสั่งจำนวนมากปริมาณงานของคำสั่งอาจน้อยกว่าความหน่วงแฝง

ค่าสำหรับJccคือ:

      Latency   Throughput
Jcc     N/A        0.5

ด้วยเชิงอรรถต่อไปนี้บนJcc:

7) การเลือกคำแนะนำการกระโดดแบบมีเงื่อนไขควรเป็นไปตามคำแนะนำของส่วน 3.4.1, "การคาดคะเนสาขา" เพื่อปรับปรุงความสามารถในการคาดการณ์ของสาขา เมื่อการทำนายกิ่งประสบความสำเร็จเวลาแฝงของjccจะเป็นศูนย์ได้อย่างมีประสิทธิภาพ

ดังนั้นไม่มีสิ่งใดในเอกสารของ Intel ที่จะปฏิบัติต่อJccคำสั่งหนึ่งที่แตกต่างไปจากเอกสารอื่น ๆ

ถ้าใครคิดเกี่ยวกับวงจรจริงที่ใช้ในการทำตามคำแนะนำเราสามารถสันนิษฐานได้ว่าจะมีประตู AND / หรือง่าย ๆ บนบิตต่างEFLAGSๆ เพื่อตรวจสอบว่าเป็นไปตามเงื่อนไขหรือไม่ ดังนั้นไม่มีเหตุผลว่าคำสั่งการทดสอบสองบิตควรใช้เวลามากกว่าหรือน้อยกว่าการทดสอบเพียงครั้งเดียว (ละเว้นการหน่วงเวลาการแพร่กระจายเกตซึ่งน้อยกว่าระยะเวลาของนาฬิกามาก)


แก้ไข: Floating Point

สิ่งนี้ถือเป็นจริงสำหรับจุดลอยตัว x87 เช่นกัน: (รหัสเดียวกันสวยมากด้านบน แต่doubleแทนที่จะเป็นint)

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
        fstp    st(0)
        seta    al                     ; Set al if above (CF=0 and ZF=0).
        test    al, al
        je      .L2
        ; Do something 1
.L2:

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; (same thing as above)
        fstp    st(0)
        setae   al                     ; Set al if above or equal (CF=0).
        test    al, al
        je      .L5
        ; Do something 2
.L5:
        leave
        ret

239
@Dyppl จริงjgและjnleเป็นคำสั่งเดียวกัน7F:-)
Jonathon Reinhart

17
ไม่ต้องพูดถึงว่าเครื่องมือเพิ่มประสิทธิภาพสามารถปรับเปลี่ยนรหัสได้แน่นอนหากตัวเลือกหนึ่งเร็วกว่าตัวเลือกอื่น
Elazar Leibovich

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

22
@ jontejj ฉันรู้อย่างมากว่า คุณได้อ่านคำตอบของฉันหรือยัง ฉันไม่ได้ระบุอะไรเกี่ยวกับคำแนะนำจำนวนเดียวกันฉันกล่าวว่าพวกเขาได้รวบรวมคำแนะนำที่เหมือนกันเป็นหลักยกเว้นคำสั่งการกระโดดอย่างใดอย่างหนึ่งกำลังดูธงหนึ่งและคำแนะนำการกระโดดอื่น ๆ กำลังดูธงสองธง ฉันเชื่อว่าฉันได้รับมากกว่าหลักฐานเพียงพอที่จะแสดงว่าพวกเขามีความหมายเหมือนกัน
Jonathon Reinhart

2
@jontejj คุณทำให้เป็นจุดที่ดีมาก สำหรับการมองเห็นมากที่สุดเท่าที่คำตอบนี้จะได้รับฉันอาจจะให้ล้างข้อมูลเล็กน้อย ขอบคุณสำหรับความคิดเห็น.
Jonathon Reinhart

593

ในอดีต (เรากำลังพูดถึงปี 1980 และต้นปี 1990) มีสถาปัตยกรรมบางอย่างที่สิ่งนี้เป็นจริง ปัญหาหลักคือการเปรียบเทียบจำนวนเต็มถูกนำมาใช้อย่างแท้จริงผ่านการลบจำนวนเต็ม สิ่งนี้ก่อให้เกิดกรณีต่อไปนี้

Comparison     Subtraction
----------     -----------
A < B      --> A - B < 0
A = B      --> A - B = 0
A > B      --> A - B > 0

ตอนนี้เมื่อA < Bการลบต้องยืมบิตสูงเพื่อให้การลบนั้นถูกต้องเหมือนกับที่คุณพกพาและยืมเมื่อบวกและลบด้วยมือ บิตที่ "ยืม" นี้มักถูกเรียกว่าบิตพกพาและสามารถทดสอบได้โดยคำสั่งสาขา บิตที่สองที่เรียกว่าzero bitจะถูกตั้งค่าหากการลบเป็นศูนย์เหมือนกันซึ่งบ่งบอกถึงความเท่าเทียมกัน

โดยปกติจะมีคำสั่งสาขาที่มีเงื่อนไขอย่างน้อยสองคำสั่งหนึ่งคำสั่งต่อสาขาในบิตนำติดตัวและอีกใบหนึ่งบนศูนย์บิต

ทีนี้เพื่อให้ได้ใจความสำคัญลองขยายตารางก่อนหน้าเพื่อรวมผลลัพธ์ของการถือและศูนย์บิต

Comparison     Subtraction  Carry Bit  Zero Bit
----------     -----------  ---------  --------
A < B      --> A - B < 0    0          0
A = B      --> A - B = 0    1          1
A > B      --> A - B > 0    1          0

ดังนั้นการใช้สาขาสำหรับA < Bสามารถทำได้ในหนึ่งคำสั่งเพราะบิตพกพามีความชัดเจนเฉพาะในกรณีนี้นั่นคือ

;; Implementation of "if (A < B) goto address;"
cmp  A, B          ;; compare A to B
bcz  address       ;; Branch if Carry is Zero to the new address

แต่ถ้าเราต้องการทำการเปรียบเทียบที่น้อยกว่าหรือเท่ากับเราต้องทำการตรวจสอบเพิ่มเติมของค่าสถานะศูนย์เพื่อตรวจสอบกรณีของความเท่าเทียมกัน

;; Implementation of "if (A <= B) goto address;"
cmp A, B           ;; compare A to B
bcz address        ;; branch if A < B
bzs address        ;; also, Branch if the Zero bit is Set

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


10
นอกจากนี้สถาปัตยกรรมเช่น x86 ใช้คำแนะนำเช่นjgeซึ่งทดสอบทั้งแฟล็ก zero และ sign / carry
greyfade

3
แม้ว่ามันจะเป็นจริงสำหรับสถาปัตยกรรมที่กำหนด อัตราต่อรองที่นักเขียนคอมไพเลอร์ไม่เคยสังเกตเห็นคืออะไรและเพิ่มการเพิ่มประสิทธิภาพเพื่อแทนที่ช้าลงด้วยเร็วขึ้น?
Jon Hanna

8
นี่เป็นความจริงใน 8080 มันมีคำแนะนำในการกระโดดเป็นศูนย์และกระโดดขึ้นไปบนลบ แต่ไม่มีใครที่สามารถทดสอบทั้งสองพร้อมกัน

4
นี่เป็นกรณีของตระกูลโปรเซสเซอร์ 6502 และ 65816 ซึ่งรวมไปถึง Motorola 68HC11 / 12 ด้วย
ลูคัส

31
แม้ใน 8080 <=ทดสอบสามารถดำเนินการในหนึ่งในการเรียนการสอนที่มีการแลกเปลี่ยนตัวถูกดำเนินการและการทดสอบสำหรับnot <(เทียบเท่า>=) นี้เป็นที่ต้องการด้วยตัวถูกดำเนินการสลับ:<= cmp B,A; bcs addrนั่นเป็นเหตุผลที่ทำให้การทดสอบนี้ถูกละเว้นโดย Intel พวกเขาคิดว่ามันซ้ำซ้อนและคุณไม่สามารถซื้อคำแนะนำที่ซ้ำซ้อนได้ในเวลานั้น :-)
Gunther Piez

92

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

ถ้ามีบางแพลตฟอร์มที่<เร็วกว่า<=ชนิดจำนวนเต็มง่ายเรียบเรียงควรเสมอแปลง<=ไป<สำหรับค่าคงที่ คอมไพเลอร์ใด ๆ ที่ไม่เพียง แต่จะเป็นคอมไพเลอร์ที่ไม่ดี (สำหรับแพลตฟอร์มนั้น)


6
+1 ฉันเห็นด้วย ทั้ง<มิได้<=มีความเร็วจนกว่าคอมไพเลอร์ตัดสินใจที่ความเร็วพวกเขาจะมี นี่คือการเพิ่มประสิทธิภาพที่ง่ายมากสำหรับคอมไพเลอร์เมื่อคุณพิจารณาว่าพวกเขาโดยทั่วไปแล้วดำเนินการเพิ่มประสิทธิภาพรหัสตายเพิ่มประสิทธิภาพโทรหางห่วงรอก (และ unrolling ในโอกาส) parallelisation อัตโนมัติของลูปต่างๆ ฯลฯ ... ทำไมเสียเวลาขบคิด optimisations ก่อนวัยอันควร ? รับการทำงานต้นแบบสร้างโปรไฟล์เพื่อกำหนดว่าการเพิ่มประสิทธิภาพที่สำคัญที่สุดอยู่ที่ใดดำเนินการปรับให้เหมาะสมตามลำดับความสำคัญและโปรไฟล์อีกครั้งตามวิธีการวัดความก้าวหน้า ...
autistic

ยังมีบางกรณีขอบที่การเปรียบเทียบที่มีค่าคงที่หนึ่งอาจช้ากว่าภายใต้ <=, เช่น, เมื่อการแปลงจาก(a < C)เป็น(a <= C-1)(สำหรับค่าคงที่บางค่าC) ทำให้Cการเข้ารหัสในชุดคำสั่งนั้นยากขึ้น ตัวอย่างเช่นชุดคำสั่งอาจสามารถแสดงค่าคงที่ที่ลงนามแล้วจาก -127 ถึง 128 ในรูปแบบกะทัดรัดในการเปรียบเทียบ แต่ค่าคงที่นอกช่วงนั้นต้องโหลดโดยใช้การเข้ารหัสที่ยาวกว่าช้าลงหรือการสอนอื่นทั้งหมด ดังนั้นการเปรียบเทียบเช่น(a < -127)นั้นอาจไม่มีการเปลี่ยนแปลงอย่างตรงไปตรงมา
BeeOnRope

@BeeOnRope ปัญหาไม่ใช่ว่าการดำเนินการที่แตกต่างกันเนื่องจากมีค่าคงที่ที่แตกต่างกันอาจส่งผลกระทบต่อประสิทธิภาพการทำงาน แต่การแสดงการดำเนินการเดียวกันโดยใช้ค่าคงที่ต่างกันอาจส่งผลกระทบต่อประสิทธิภาพการทำงาน ดังนั้นเราจึงไม่ได้เปรียบเทียบa > 127ไปa > 128เพราะคุณไม่มีทางเลือกมีคุณใช้หนึ่งที่คุณต้องการ เรากำลังเปรียบเทียบa > 127กับa >= 128ซึ่งไม่สามารถต้องการการเข้ารหัสที่แตกต่างกันหรือคำแนะนำที่แตกต่างกันเพราะพวกเขามีตารางความจริงเดียวกัน การเข้ารหัสของอันใดอันหนึ่งเป็นการเข้ารหัสของอีกฝ่ายหนึ่งอย่างเท่าเทียมกัน
David Schwartz

ฉันกำลังตอบกลับโดยทั่วไปต่อคำสั่งของคุณว่า "หากมีบางแพลตฟอร์มที่ [<= ช้าลง] คอมไพเลอร์ควรเปลี่ยน<=เป็น<ค่าคงที่เสมอ" เท่าที่ฉันรู้การเปลี่ยนแปลงนั้นเกี่ยวข้องกับการเปลี่ยนค่าคงที่ เช่นa <= 42ถูกรวบรวมa < 43เพราะ<เร็วกว่า ในบางกรณีการเปลี่ยนแปลงดังกล่าวจะไม่เกิดผลเพราะค่าคงที่ใหม่อาจต้องการคำแนะนำมากกว่าหรือช้ากว่า แน่นอนa > 127และa >= 128เทียบเท่าและคอมไพเลอร์ควรเข้ารหัสทั้งสองแบบในวิธีที่เร็วที่สุด (เหมือนกัน) แต่นั่นไม่ได้ขัดแย้งกับสิ่งที่ฉันพูด
BeeOnRope

67

ฉันเห็นว่าไม่เร็วขึ้น คอมไพเลอร์สร้างรหัสเครื่องเดียวกันในแต่ละเงื่อนไขด้วยค่าที่แตกต่างกัน

if(a < 901)
cmpl  $900, -4(%rbp)
jg .L2

if(a <=901)
cmpl  $901, -4(%rbp)
jg .L3

ตัวอย่างของฉันifมาจาก GCC บนแพลตฟอร์ม x86_64 บน Linux

นักเขียนคอมไพเลอร์เป็นคนฉลาดและพวกเขาคิดว่าสิ่งเหล่านี้และอื่น ๆ อีกมากมายที่พวกเราส่วนใหญ่ยอมรับ

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

int b;
if(a < b)
cmpl  -4(%rbp), %eax
jge   .L2

if(a <=b)
cmpl  -4(%rbp), %eax
jg .L3

9
โปรดทราบว่านี่เฉพาะ x86
Michael Petrotta

10
ฉันคิดว่าคุณควรใช้ว่าif(a <=900)แสดงให้เห็นว่าจะสร้างตรง asm เดียวกัน :)
Lipis

2
@AdrianCornish ขออภัย .. ฉันแก้ไขมัน .. มันมากขึ้นหรือน้อยลงเหมือนกัน .. แต่ถ้าคุณเปลี่ยนวินาทีถ้าเป็น <= 900 รหัส asm จะเหมือนกัน :) มันเหมือนกันตอนนี้ .. แต่คุณ ทราบ .. สำหรับโรค :) ความ
Lipis

3
@Boann นั่นอาจลดลงถ้า (จริง) และตัดออกอย่างสมบูรณ์
Qsario

5
ไม่มีใครชี้ให้เห็นว่าการเพิ่มประสิทธิภาพนี้ใช้กับการเปรียบเทียบคงที่เท่านั้น ฉันรับประกันได้ว่ามันจะไม่ถูกทำเช่นนี้เพื่อเปรียบเทียบสองตัวแปร
Jonathon Reinhart

51

สำหรับรหัสจุดลอยตัว <= การเปรียบเทียบอาจช้ากว่าเดิม (โดยคำสั่งเดียว) แม้ในสถาปัตยกรรมสมัยใหม่ นี่คือฟังก์ชั่นแรก:

int compare_strict(double a, double b) { return a < b; }

บน PowerPC อันดับแรกจะทำการเปรียบเทียบจุดลอยตัว (ซึ่งอัพเดตcrการลงทะเบียนเงื่อนไข) จากนั้นย้ายการลงทะเบียนเงื่อนไขไปยัง GPR เลื่อนบิต "เปรียบเทียบน้อยกว่า" เข้าที่แล้วส่งกลับ ใช้เวลาสี่คำแนะนำ

ตอนนี้ให้พิจารณาฟังก์ชั่นนี้แทน:

int compare_loose(double a, double b) { return a <= b; }

สิ่งนี้ต้องใช้งานเดียวกันกับcompare_strictด้านบน แต่ตอนนี้มีสองบิตที่น่าสนใจ: "น้อยกว่า" และ "เท่ากับ" สิ่งนี้ต้องการคำสั่งพิเศษ ( cror- เงื่อนไข register bitwise OR) เพื่อรวมสองบิตเหล่านี้เป็นหนึ่งเดียว ดังนั้นcompare_looseต้องมีห้าคำแนะนำในขณะที่compare_strictต้องการสี่

คุณอาจคิดว่าคอมไพเลอร์สามารถปรับฟังก์ชันที่สองให้เหมาะสมดังนี้:

int compare_loose(double a, double b) { return ! (a > b); }

อย่างไรก็ตามสิ่งนี้จะจัดการกับ NaN ไม่ถูกต้อง NaN1 <= NaN2และNaN1 > NaN2จำเป็นต้องประเมินเป็นเท็จทั้งคู่


โชคดีที่มันไม่ทำงานอย่างนี้ใน x86 (x87) fucomipชุด ZF และ CF
Jonathon Reinhart

3
@ JonathonReinhart: ฉันคิดว่าคุณเข้าใจผิดว่า PowerPC กำลังทำอะไร - การลงทะเบียนเงื่อนไขcr นั้นเทียบเท่ากับค่าสถานะที่เหมือนกับZFและCFบน x86 (แม้ว่า CR จะมีความยืดหยุ่นมากกว่า) สิ่งที่ผู้โพสต์กำลังพูดถึงคือการย้ายผลลัพธ์ไปยัง GPR: ซึ่งใช้สองคำสั่งใน PowerPC แต่ x86 มีคำสั่งการย้ายแบบมีเงื่อนไข
Dietrich Epp

@DietrichEpp สิ่งที่ฉันหมายถึงการเพิ่มหลังจากคำสั่งของฉันคือ: ซึ่งคุณสามารถกระโดดได้ทันทีตามมูลค่าของ EFLAGS ขออภัยที่ไม่ชัดเจน
Jonathon Reinhart

1
@ JonathonReinhart: ใช่แล้วคุณสามารถกระโดดได้ทันทีตามมูลค่าของ CR คำตอบไม่ได้พูดถึงการกระโดดซึ่งเป็นคำแนะนำเพิ่มเติมที่มาจาก
Dietrich Epp

34

บางทีผู้เขียนหนังสือที่ไม่มีชื่อนั้นได้อ่านว่าa > 0ทำงานได้เร็วกว่าa >= 1และคิดว่าเป็นเรื่องจริงในระดับสากล

แต่มันเป็นเพราะ0มีส่วนเกี่ยวข้อง (เพราะCMPสามารถขึ้นอยู่กับสถาปัตยกรรมแทนที่เช่นกับOR) <และไม่ได้เพราะของ


1
แน่นอนว่าใน "แก้ปัญหา" สร้าง แต่มันจะใช้คอมไพเลอร์ที่ไม่ดีสำหรับการ(a >= 1)ทำงานช้ากว่า(a > 0)ตั้งแต่อดีตสามารถเปลี่ยนนิด ๆ หลังโดยเพิ่มประสิทธิภาพ ..
BeeOnRope

2
@BeeOnRope บางครั้งฉันรู้สึกประหลาดใจที่เครื่องมือเพิ่มประสิทธิภาพสามารถเพิ่มประสิทธิภาพและสิ่งที่ง่าย ๆ ที่ล้มเหลวในการทำเช่นนั้น
glglgl

1
แน่นอนและมันก็คุ้มค่าที่จะตรวจสอบเอาต์พุต asm สำหรับฟังก์ชั่นน้อยมากที่มันจะสำคัญ ที่กล่าวว่าการเปลี่ยนแปลงข้างต้นเป็นพื้นฐานมากและได้รับการดำเนินการแม้ในคอมไพเลอร์ง่าย ๆ มานานหลายทศวรรษ
BeeOnRope

32

อย่างน้อยที่สุดถ้านี่เป็นความจริงคอมไพเลอร์สามารถเพิ่มประสิทธิภาพเล็กน้อย <= b เป็น! (a> b), และดังนั้นแม้ว่าการเปรียบเทียบเองจะช้ากว่าจริง ๆ แต่คอมไพเลอร์ไร้เดียงสาที่สุดคุณจะไม่สังเกตเห็นความแตกต่าง .


ทำไม! (a> b) เป็นรุ่นที่ได้รับการเพิ่มประสิทธิภาพของ <= b Isnt! (a> b) 2 การทำงานในหนึ่งเดียว?
Abhishek Singh เมื่อ

6
@AbhishekSingh NOTถูกสร้างขึ้นโดยคำสั่งอื่น ( jevs. jne)
Pavel Gatnar

15

พวกเขามีความเร็วเท่ากัน บางทีในสถาปัตยกรรมพิเศษบางอย่างที่เขา / เธอพูดถูกต้อง แต่ในตระกูล x86 อย่างน้อยฉันก็รู้ว่าพวกเขาเหมือนกัน เนื่องจากการทำเช่นนี้ CPU จะทำการลบ (a - b) จากนั้นตรวจสอบการตั้งค่าสถานะของการลงทะเบียนค่าสถานะ บิตสองบิตของรีจิสเตอร์นั้นเรียกว่า ZF (ศูนย์แฟล็ก) และ SF (แฟล็กเครื่องหมาย) และจะทำในรอบเดียวเพราะมันจะทำด้วยการดำเนินการหน้ากาก


14

สิ่งนี้จะขึ้นอยู่กับสถาปัตยกรรมพื้นฐานที่ C ถูกคอมไพล์ด้วย ตัวประมวลผลและสถาปัตยกรรมบางอย่างอาจมีคำแนะนำที่ชัดเจนว่าเท่ากับหรือน้อยกว่าและเท่ากับซึ่งดำเนินการในจำนวนรอบที่แตกต่างกัน

นั่นอาจจะเป็นเรื่องที่ผิดปกติเพราะคอมไพเลอร์สามารถแก้ไขได้ทำให้มันไม่เกี่ยวข้อง


1
หากมีความแตกต่างใน cyles 1) มันจะไม่สามารถตรวจพบได้ 2) คอมไพเลอร์ใด ๆ ที่มีมูลค่าเกลือจะทำการแปลงจากรูปแบบช้าไปเป็นรูปแบบที่เร็วขึ้นโดยไม่เปลี่ยนความหมายของรหัส ดังนั้นคำสั่งที่ได้จากการปลูกจะเหมือนกัน
Martin York

ตกลงอย่างสมบูรณ์มันจะเป็นความแตกต่างเล็กน้อยและโง่ในกรณีใด ๆ ไม่มีอะไรจะพูดถึงในหนังสือที่ควรจะเป็นผู้ไม่เชื่อเรื่องพระเจ้าแพลตฟอร์ม
Telgin

@lttlrck: ฉันเข้าใจแล้ว เอาฉันไปซักพัก ไม่พวกเขาไม่สามารถตรวจจับได้เพราะมีสิ่งอื่น ๆ อีกมากมายเกิดขึ้นที่ทำให้การวัดของพวกเขาเป็นไปไม่ได้ หน่วยประมวลผลกลาง / แคชคิดถึง / สัญญาณ / การแลกเปลี่ยนกระบวนการ ดังนั้นในสถานการณ์ระบบปฏิบัติการปกติในระดับรอบเดียวไม่สามารถวัดได้ทางร่างกาย หากคุณสามารถขจัดสิ่งรบกวนที่เกิดจากการวัดของคุณ (รันบนชิปที่มีหน่วยความจำออนบอร์ดและไม่มีระบบปฏิบัติการ) จากนั้นคุณยังมีตัวจับเวลาจำนวนมากที่คุณต้องกังวล แต่ในทางทฤษฎีถ้าคุณใช้มันนานพอ
Martin York

12

TL; DRคำตอบ

สำหรับการผสมผสานส่วนใหญ่ของสถาปัตยกรรมคอมไพเลอร์และภาษามันจะไม่เร็วขึ้น

คำตอบแบบเต็ม

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

ดังนั้นฉันขอแนะนำว่าการเพิ่มประสิทธิภาพขนาดเล็กประเภทนี้เป็นตัวอย่างของลัทธิการขนส่งสินค้าการเขียนโปรแกรมมากกว่าการปฏิบัติทางวิศวกรรมซอฟต์แวร์ที่ดีที่สุด

อาจมีสถาปัตยกรรมบางส่วนที่นี่เป็นการเพิ่มประสิทธิภาพ แต่ฉันรู้ว่าอย่างน้อยหนึ่งสถาปัตยกรรมที่ตรงกันข้ามอาจเป็นจริง สถาปัตยกรรมTransputer ที่น่าเชื่อถือมีเพียงคำแนะนำของรหัสเครื่องที่เท่ากันและมากกว่าหรือเท่ากันดังนั้นการเปรียบเทียบทั้งหมดจะต้องถูกสร้างขึ้นจากแบบดั้งเดิมเหล่านี้

ถึงกระนั้นในเกือบทุกกรณีคอมไพเลอร์สามารถสั่งการประเมินผลในลักษณะที่ในทางปฏิบัติไม่มีการเปรียบเทียบใด ๆ ที่ได้เปรียบกว่าคนอื่น ๆ กรณีที่เลวร้ายที่สุดมันอาจจำเป็นต้องเพิ่มคำสั่งกลับรายการ (REV) เพื่อสลับรายการสองอันดับแรกบนตัวถูกดำเนินการสแต็ก นี่เป็นคำสั่งไบต์เดียวซึ่งใช้รอบเดียวในการรันดังนั้นจึงมีค่าใช้จ่ายน้อยที่สุดเท่าที่จะเป็นไปได้

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


6

คุณไม่ควรสังเกตเห็นความแตกต่างแม้ว่าจะมีก็ตาม นอกจากนี้ในทางปฏิบัติคุณจะต้องทำการเพิ่มเติมa + 1หรือa - 1ทำให้สภาพคงอยู่เว้นแต่คุณจะใช้ค่าคงที่เวทมนตร์ซึ่งเป็นการปฏิบัติที่แย่มากโดยวิธีการทั้งหมด


1
การปฏิบัติที่ไม่ดีคืออะไร? การเพิ่มหรือลดเคาน์เตอร์? คุณเก็บสัญกรณ์ดัชนีไว้อย่างไร
jcolebrand

5
เขาหมายถึงว่าคุณกำลังทำการเปรียบเทียบตัวแปร 2 ประเภท แน่นอนว่ามันไม่สำคัญหากคุณกำลังตั้งค่าสำหรับลูปหรือบางอย่าง แต่ถ้าคุณมี x <= y และไม่ทราบว่า y มันจะช้ากว่าการ 'ปรับ' ให้เป็น x <y + 1
JustinDanielson

@JustinDanielson เห็นด้วย ไม่พูดถึงน่าเกลียดสับสน ฯลฯ
Jonathon Reinhart

4

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


ฉันค่อนข้างไม่เห็นด้วย ในการเขียนโปรแกรมเชิงแข่งขันภาษาสคริปต์มักจะเสนอวิธีแก้ปัญหาที่เร็วที่สุด แต่ต้องใช้เทคนิคที่ถูกต้อง (อ่าน: การปรับให้เหมาะสม) เพื่อให้ได้โซลูชันที่ถูกต้อง
Tyler Crompton

3

เมื่อผมเขียนคำตอบนี้ผมเป็นเพียงการมองหาที่เกี่ยวกับคำถามชื่อ <เทียบกับ <= โดยทั่วไปไม่ได้เป็นตัวอย่างที่เฉพาะเจาะจงของค่าคงที่เมื่อเทียบกับa < 901 a <= 900คอมไพเลอร์หลายคนมักจะลดขนาดของค่าคงที่โดยการแปลงระหว่าง<และ<=เช่น e เนื่องจาก x86 ตัวถูกดำเนินการทันทีมีการเข้ารหัส 1 ไบต์ที่สั้นกว่าสำหรับ -128..127

สำหรับ ARM และ AArch64 โดยเฉพาะความสามารถในการเข้ารหัสแบบทันทีนั้นขึ้นอยู่กับความสามารถในการหมุนฟิลด์แคบ ๆ ให้อยู่ในตำแหน่งใด ๆ ในคำ ดังนั้นcmp w0, #0x00f000จะเข้ารหัสได้ในขณะที่cmp w0, #0x00effffอาจจะไม่ ดังนั้นกฎที่ทำให้เล็กลงสำหรับการเปรียบเทียบกับค่าคงที่เวลาคอมไพล์ไม่ได้ใช้กับ AArch64 เสมอไป


<vs. <= โดยทั่วไปรวมถึงเงื่อนไขตัวแปรรันไทม์

ในภาษาประกอบในเครื่องมากที่สุดสำหรับการเปรียบเทียบมีค่าใช้จ่ายเช่นเดียวกับการเปรียบเทียบหา<= <สิ่งนี้นำไปใช้ไม่ว่าคุณจะทำการแยกมันบูลีนเพื่อสร้างจำนวนเต็ม 0/1 หรือใช้เป็นเพรดิเคตสำหรับการดำเนินการเลือกแบบไม่มีสาขา (เช่น x86 CMOV) คำตอบอื่น ๆ ได้กล่าวถึงส่วนนี้ของคำถามเท่านั้น

แต่คำถามนี้เกี่ยวกับตัวดำเนินการ C ++ คืออินพุตของเครื่องมือเพิ่มประสิทธิภาพ โดยปกติพวกเขาทั้งสองมีประสิทธิภาพเท่าเทียมกัน คำแนะนำจากหนังสือเล่มนี้ฟังดูผิด ๆ โดยสิ้นเชิงเพราะคอมไพเลอร์สามารถเปลี่ยนการเปรียบเทียบที่พวกเขาใช้ใน asm แต่มีข้อยกเว้นอย่างน้อยหนึ่งข้อที่การใช้งาน<=สามารถสร้างบางอย่างที่คอมไพเลอร์ไม่สามารถปรับให้เหมาะสม

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

Overflow ที่ไม่ได้ลงนามถูกกำหนดอย่างดีว่าเป็น base-2 ที่ล้อมรอบซึ่งต่างจาก overflow ที่ลงนามแล้ว (UB) โดยทั่วไปแล้วตัวนับลูปที่ลงนามจะปลอดภัยจากสิ่งนี้ด้วยคอมไพเลอร์ที่ปรับให้เหมาะสมตาม UB ที่มีการลงชื่อเกินไม่ได้เกิดขึ้น: ++i <= sizeในที่สุดจะกลายเป็นเท็จเสมอ ( สิ่งที่โปรแกรมเมอร์ C ทุกคนควรรู้เกี่ยวกับพฤติกรรมที่ไม่ได้กำหนด )

void foo(unsigned size) {
    unsigned upper_bound = size - 1;  // or any calculation that could produce UINT_MAX
    for(unsigned i=0 ; i <= upper_bound ; i++)
        ...

คอมไพเลอร์สามารถปรับให้เหมาะสมในวิธีที่รักษาพฤติกรรม (ที่กำหนดและสามารถสังเกตได้ตามกฎหมาย) ของแหล่ง C ++ สำหรับค่าอินพุตที่เป็นไปได้ทั้งหมดยกเว้นที่นำไปสู่พฤติกรรมที่ไม่ได้กำหนด

(เรียบง่ายi <= sizeจะสร้างปัญหาด้วย แต่ฉันคิดว่าการคำนวณขอบเขตบนเป็นตัวอย่างที่สมจริงยิ่งขึ้นของการแนะนำความเป็นไปได้ของการวนซ้ำไม่สิ้นสุดสำหรับอินพุตที่คุณไม่สนใจ แต่ต้องใช้คอมไพเลอร์พิจารณา)

ในกรณีนี้size=0นำไปสู่upper_bound=UINT_MAXและi <= UINT_MAXเป็นจริงเสมอ ดังนั้นลูปนี้จึงไม่มีที่สิ้นสุดsize=0และคอมไพเลอร์ต้องเคารพว่าแม้ว่าคุณในฐานะโปรแกรมเมอร์อาจไม่เคยตั้งใจจะส่งผ่าน size = 0 ถ้าคอมไพเลอร์สามารถ inline ฟังก์ชั่นนี้ในการโทรที่มันสามารถพิสูจน์ได้ว่าขนาด = 0 i < sizeเป็นไปไม่ได้แล้วที่ดีก็สามารถเพิ่มประสิทธิภาพเหมือนมันสามารถสำหรับ

Asm like if(!size) skip the loop; do{...}while(--size);เป็นวิธีหนึ่งที่มีประสิทธิภาพในการเพิ่มประสิทธิfor( i<size )ภาพลูปถ้าiไม่จำเป็นต้องใช้ค่าจริงของลูปในลูป )

แต่นั่นทำ {} ในขณะที่ไม่สามารถสิ้นสุดได้: หากป้อนด้วยsize==0เราจะได้รับซ้ำ 2 ^ n (การวนซ้ำกับจำนวนเต็มที่ไม่ได้ลงนามทั้งหมดใน for for C ทำให้สามารถแสดงลูปมากกว่าจำนวนเต็มที่ไม่ได้ลงนามทั้งหมดรวมถึงศูนย์ แต่มันไม่ใช่เรื่องง่ายหากไม่มีแฟล็กที่ถือในแบบ asm)

ด้วยการวนซ้ำของตัวนับลูปทำให้มีความเป็นไปได้คอมไพเลอร์สมัยใหม่มักจะ "ยอมแพ้" และไม่ปรับให้เหมาะสมเกือบจะก้าวร้าว

ตัวอย่าง: ผลรวมของจำนวนเต็มตั้งแต่ 1 ถึง n

การใช้การi <= nรับรู้สำนวนเสียงดังกังวานที่ไม่ได้ลงนามของ clang ที่ปรับsum(1 .. n)ลูปให้เหมาะสมกับรูปแบบปิดตามn * (n+1) / 2สูตรของ Gauss

unsigned sum_1_to_n_finite(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i < n+1 ; ++i)
        total += i;
    return total;
}

x86-64 asm จาก clang7.0 และ gcc8.2 บนตัวรวบรวมคอมไพเลอร์ Godbolt

 # clang7.0 -O3 closed-form
    cmp     edi, -1       # n passed in EDI: x86-64 System V calling convention
    je      .LBB1_1       # if (n == UINT_MAX) return 0;  // C++ loop runs 0 times
          # else fall through into the closed-form calc
    mov     ecx, edi         # zero-extend n into RCX
    lea     eax, [rdi - 1]   # n-1
    imul    rax, rcx         # n * (n-1)             # 64-bit
    shr     rax              # n * (n-1) / 2
    add     eax, edi         # n + (stuff / 2) = n * (n+1) / 2   # truncated to 32-bit
    ret          # computed without possible overflow of the product before right shifting
.LBB1_1:
    xor     eax, eax
    ret

แต่สำหรับรุ่นที่ไร้เดียงสาเราเพิ่งได้ลูปลูปจากเสียงดังกราว

unsigned sum_1_to_n_naive(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i<=n ; ++i)
        total += i;
    return total;
}
# clang7.0 -O3
sum_1_to_n(unsigned int):
    xor     ecx, ecx           # i = 0
    xor     eax, eax           # retval = 0
.LBB0_1:                       # do {
    add     eax, ecx             # retval += i
    add     ecx, 1               # ++1
    cmp     ecx, edi
    jbe     .LBB0_1            # } while( i<n );
    ret

GCC ไม่ได้ใช้แบบฟอร์มการปิดทางใดทางหนึ่งดังนั้นทางเลือกของสภาพ loop ไม่ได้จริงๆมันเจ็บปวด ; มันจะปรับเวกเตอร์อัตโนมัติด้วยการเพิ่มจำนวนเต็มของ SIMD โดยรัน 4 iค่าแบบขนานในองค์ประกอบของการลงทะเบียน XMM

# "naive" inner loop
.L3:
    add     eax, 1       # do {
    paddd   xmm0, xmm1    # vect_total_4.6, vect_vec_iv_.5
    paddd   xmm1, xmm2    # vect_vec_iv_.5, tmp114
    cmp     edx, eax      # bnd.1, ivtmp.14     # bound and induction-variable tmp, I think.
    ja      .L3 #,       # }while( n > i )

 "finite" inner loop
  # before the loop:
  # xmm0 = 0 = totals
  # xmm1 = {0,1,2,3} = i
  # xmm2 = set1_epi32(4)
 .L13:                # do {
    add     eax, 1       # i++
    paddd   xmm0, xmm1    # total[0..3] += i[0..3]
    paddd   xmm1, xmm2    # i[0..3] += 4
    cmp     eax, edx
    jne     .L13      # }while( i != upper_limit );

     then horizontal sum xmm0
     and peeled cleanup for the last n%3 iterations, or something.

มันมีลูปสเกลาร์ธรรมดาซึ่งฉันคิดว่ามันใช้สำหรับขนาดเล็กมากnและ / หรือสำหรับกรณีลูปไม่สิ้นสุด

BTW, ลูปทั้งสองนี้เสียคำสั่ง (และ uop บน Sandybridge-family CPUs) บนลูปโอเวอร์เฮด sub eax,1/ jnzแทนที่จะเป็นadd eax,1/ cmp / jcc จะมีประสิทธิภาพมากกว่า 1 uop แทน 2 (หลังแมโครฟิวชั่นของ sub / jcc หรือ cmp / jcc) รหัสหลังจากลูปทั้งสองเขียน EAX โดยไม่มีเงื่อนไขดังนั้นจึงไม่ได้ใช้ค่าสุดท้ายของตัวนับลูป


ตัวอย่างที่วางแผนไว้อย่างดี ความคิดเห็นอื่นของคุณเกี่ยวกับผลกระทบที่อาจเกิดขึ้นจากการดำเนินการตามคำสั่งเนื่องจาก EFLAGS ใช้หรือไม่ มันเป็นทางทฤษฎีล้วนๆหรืออาจเกิดขึ้นจริงได้ว่า JB นำไปสู่การวางท่อที่ดีกว่า JBE หรือไม่?
rustyx

@rustyx: ฉันแสดงความคิดเห็นว่ามีคำตอบอื่นอีกไหม? คอมไพเลอร์จะไม่ได้ไปปล่อยรหัสที่เป็นสาเหตุของแผงลอยบางส่วนธงและไม่แน่นอนสำหรับ C หรือ< <=แต่แน่นอนว่าtest ecx,ecx/ bt eax, 3/ jbeจะกระโดดถ้าตั้งค่า ZF (ecx == 0) หรือถ้า CF ถูกตั้งค่า (บิต 3 ของ EAX == 1) ทำให้เกิดการตั้งค่าสถานะบางส่วนบน CPU ส่วนใหญ่เนื่องจากค่าสถานะที่อ่านไม่ได้ทั้งหมด มาจากคำสั่งสุดท้ายเพื่อเขียนแฟล็กใด ๆ สำหรับครอบครัว Sandybridge มันไม่ได้หยุดนิ่งจริง ๆ เพียงแค่ต้องการแทรก uop ที่รวมเข้าด้วยกัน cmp/ testเขียนการตั้งค่าสถานะทั้งหมด แต่btปล่อยให้ ZF ไม่ได้แก้ไข felixcloutier.com/x86/bt
Peter Cordes

2

เฉพาะในกรณีที่ผู้สร้างคอมพิวเตอร์ไม่ดีด้วยตรรกะแบบบูล ซึ่งพวกเขาไม่ควรจะเป็น

ทุกการเปรียบเทียบ ( >= <= > <) สามารถทำได้ในความเร็วเดียวกัน

การเปรียบเทียบคือการลบ (ความแตกต่าง) และดูว่ามันเป็นบวก / ลบ
(หากmsbตั้งค่าไว้หมายเลขจะเป็นลบ)

วิธีการตรวจสอบa >= b? ย่อยa-b >= 0ตรวจสอบว่าa-bเป็นบวก
วิธีการตรวจสอบa <= b? ย่อย0 <= b-aตรวจสอบว่าb-aเป็นบวก
วิธีการตรวจสอบa < b? a-b < 0ตรวจสอบย่อยว่าa-bเป็นลบหรือไม่
วิธีการตรวจสอบa > b? 0 > b-aตรวจสอบย่อยว่าb-aเป็นลบหรือไม่

เพียงแค่ใส่คอมพิวเตอร์ก็สามารถทำสิ่งนี้ภายใต้ประทุนสำหรับ op ที่กำหนด:

a >= b== msb(a-b)==0
a <= b== msb(b-a)==0
a > b== msb(b-a)==1
a < b==msb(a-b)==1

และแน่นอนคอมพิวเตอร์จะไม่ต้องการจริงที่จะทำ==0หรือ==1อย่างใดอย่างหนึ่ง
เพราะ==0มันสามารถย้อนกลับmsbจากวงจร

อย่างไรก็ตามพวกเขาแน่นอนที่สุดจะไม่a >= bถูกคำนวณเป็นa>b || a==blol

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