ข้อดีของการสลับคำสั่ง if-else


168

อะไรคือแนวปฏิบัติที่ดีที่สุดสำหรับการใช้switchคำสั่งกับการใช้ifคำสั่งสำหรับการunsignedแจกแจง30 รายการซึ่งประมาณ 10 รายการมีการกระทำที่คาดหวัง (ปัจจุบันคือการกระทำเดียวกัน) ต้องคำนึงถึงประสิทธิภาพและพื้นที่ แต่ไม่สำคัญ ฉันสรุปตัวอย่างแล้วดังนั้นอย่าเกลียดฉันสำหรับแบบแผนการตั้งชื่อ

switch คำให้การ:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if คำให้การ:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}

26
สิ่งนี้ได้รับการแก้ไขเป็น 'อัตนัย' หรือไม่ จริงๆ? แน่นอน 'อัตนัย' สำหรับสิ่งที่ไม่สามารถพิสูจน์ได้ทางใดทางหนึ่ง?
Alexandra Franks

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

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


@RichardFranks offtopic: grats! คุณเป็นมนุษย์คนแรกที่ได้รับการดูแลเกี่ยวกับ SO ดังนั้นฉันเคยเห็น
jungle_mole

คำตอบ:


162

ใช้สวิตช์

ในกรณีที่เลวร้ายที่สุดคอมไพเลอร์จะสร้างรหัสเดียวกันกับ if-else chain ดังนั้นคุณจะไม่สูญเสียอะไรเลย หากมีข้อสงสัยให้ใส่กรณีที่พบบ่อยที่สุดเป็นอันดับแรกในคำสั่ง switch

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


2
ในทางเทคนิคจะยังคงมีการเปรียบเทียบเพื่อให้แน่ใจว่าค่าของ enum อยู่ในตารางการกระโดด
0124816

Jep นั่นเป็นเรื่องจริง การสลับใช้ enums และการจัดการทุกกรณีอาจกำจัดการเปรียบเทียบล่าสุด
Nils Pipenbrinck

4
โปรดทราบว่าชุดของ ifs ในทางทฤษฎีสามารถวิเคราะห์ออกมาให้เหมือนกับสวิตช์โดยคอมไพเลอร์ แต่ทำไมถึงมีโอกาส ด้วยการใช้สวิตช์คุณจะสื่อสารสิ่งที่คุณต้องการอย่างแน่นอนซึ่งทำให้การสร้างโค้ดง่ายขึ้น
jakobengblom2

5
jakoben: สามารถทำได้ แต่สำหรับเชนสลับถ้า / เชนอื่น ๆ เท่านั้น ในทางปฏิบัติสิ่งเหล่านี้จะไม่เกิดขึ้นเพราะโปรแกรมเมอร์ใช้สวิตช์ ฉันขุดลงไปในเทคโนโลยีคอมไพเลอร์และเชื่อฉัน: การหาสิ่งก่อสร้างที่ไร้ประโยชน์นั้นใช้เวลานานมาก สำหรับคอมไพเลอร์พวกนั้นการเพิ่มประสิทธิภาพจะทำให้รู้สึก
Nils Pipenbrinck

5
@ NilsPipenbrinck ด้วยความสะดวกในการสร้างหลอก - recursive if- elseเชนในการเขียนโปรแกรมแม่แบบ meta และความยากในการสร้างswitch casechain การทำแผนที่นั้นอาจมีความสำคัญมากกว่า (และใช่ความเห็นโบราณ แต่เว็บเป็นตลอดไปหรืออย่างน้อยจนถึงวันอังคารถัดไป)
Yakk - Adam Nevraumont

45

สำหรับกรณีพิเศษที่คุณระบุไว้ในตัวอย่างรหัสที่ชัดเจนที่สุดน่าจะเป็น:

if (RequiresSpecialEvent(numError))
    fire_special_event();

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

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

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


5
นี่เป็นเรื่องจริง ความสามารถในการอ่านนั้นดีกว่าทั้งสวิตช์และข้อความสั่ง if จริง ๆ แล้วฉันจะตอบบางอย่างเช่นนี้ แต่คุณเอาชนะฉันไป :-)
mlarsen

หากค่า enum ของคุณมีค่าเพียงเล็กน้อยคุณไม่จำเป็นต้องใช้แฮชเพียงแค่ใช้ตาราง เช่นการใช้งานด้วยconst std::bitset<MAXERR> specialerror(initializer); if (specialerror[numError]) { fire_special_event(); }หากคุณต้องการตรวจสอบขอบเขตbitset::test(size_t)จะมีข้อยกเว้นเกี่ยวกับค่าที่ไม่อยู่ในขอบเขต ( bitset::operator[]ไม่ได้ตรวจสอบช่วง) cplusplus.com/reference/bitset/bitset/test นี้น่าจะดีกว่าคอมไพเลอร์สร้างตารางกระโดดดำเนินการswitchทาย ในกรณีที่ไม่พิเศษซึ่งจะเป็นสาขาเดียวที่ไม่ได้ดำเนินการ
ปีเตอร์กอร์เดส

@PeterCordes ฉันยังคงยืนยันว่ามันจะดีกว่าที่จะใส่ตารางลงในฟังก์ชั่นของตัวเอง ขณะที่ผมกล่าวว่ามีจำนวนมากของตัวเลือกที่เปิดขึ้นเมื่อคุณทำอย่างนั้นผมไม่ได้พยายามที่จะระบุพวกเขาทั้งหมด
Mark Ransom

@ MarkRansom: ฉันไม่ได้ตั้งใจที่จะไม่เห็นด้วยกับการสรุปมัน เนื่องจากคุณใช้ตัวอย่างการนำไปใช้std::setฉันคิดว่าฉันอาจชี้ให้เห็นว่ามันอาจเป็นทางเลือกที่แย่ ปรากฎว่า gcc รวบรวมรหัสของ OP เพื่อทดสอบบิตแมปใน 32 บิตทันที godbolt: goo.gl/qjjv0e gcc 5.2 จะทำเช่นนี้กับifรุ่น นอกจากนี้ GCC เมื่อเร็ว ๆ นี้จะใช้การเรียนการสอนบิตทดสอบbtแทนการขยับไปใส่บิตในสถานที่ที่เหมาะสมและการใช้1 test reg, imm32
Peter Cordes

บิตแมปที่คงที่ในทันทีนี้เป็นรางวัลที่ยิ่งใหญ่เพราะไม่มีแคชบนบิตแมป ใช้งานได้หากรหัสข้อผิดพลาด "พิเศษ" ทั้งหมดอยู่ในช่วง 64 หรือน้อยกว่า (หรือ 32 สำหรับรหัส 32 บิตแบบดั้งเดิม) คอมไพเลอร์จะลบค่าตัวพิมพ์เล็กที่สุดหากไม่ใช่ศูนย์ สิ่งที่ควรหลีกเลี่ยงคือคอมไพเลอร์ล่าสุดนั้นฉลาดพอที่คุณจะได้รับโค้ดที่ดีจากตรรกะใด ๆ ก็ตามที่คุณใช้ยกเว้นว่าคุณบอกให้ใช้โครงสร้างข้อมูลขนาดใหญ่
Peter Cordes

24

สวิทช์เป็นได้เร็วขึ้น

เพียงแค่ลองถ้า / else-ing 30 ค่าที่แตกต่างกันภายในวงและเปรียบเทียบกับรหัสเดียวกันโดยใช้สวิตช์เพื่อดูว่าสวิตช์นั้นเร็วขึ้นเท่าใด

ตอนนี้สวิตช์มีปัญหาจริงหนึ่งข้อ : สวิตช์ต้องทราบเวลารวบรวมค่าภายในแต่ละกรณี ซึ่งหมายความว่ารหัสต่อไปนี้:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

จะไม่รวบรวม

คนส่วนใหญ่จะใช้กำหนด (Aargh!) และคนอื่น ๆ จะประกาศและกำหนดตัวแปรคงที่ในหน่วยการรวบรวมเดียวกัน ตัวอย่างเช่น:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

ดังนั้นในที่สุดผู้พัฒนาจะต้องเลือกระหว่าง "ความเร็ว + ความคมชัด" กับ "การมีเพศสัมพันธ์"

(ไม่ใช่ว่าสวิทช์ไม่สามารถเขียนให้สับสนเหมือนนรกได้ ... สวิทช์ส่วนใหญ่ที่ฉันเห็นในขณะนี้อยู่ในหมวดหมู่ "สับสน" "... แต่นี่เป็นอีกเรื่องหนึ่ง ... )

แก้ไข 2008-09-21:

bk1eเพิ่มความคิดเห็นต่อไปนี้: " การกำหนดค่าคงที่เป็น enums ในไฟล์ส่วนหัวเป็นอีกวิธีหนึ่งในการจัดการเรื่องนี้"

แน่นอนมันเป็น

จุดของประเภทภายนอกคือการแยกค่าจากแหล่งที่มา การกำหนดค่านี้เป็นมาโครในรูปแบบการประกาศ int แบบง่าย ๆ หรือแม้กระทั่งเมื่อ enum มีผลข้างเคียงของการฝังค่า ดังนั้นหากกำหนด, ค่า enum หรือการเปลี่ยนแปลงค่า int const, จำเป็นต้องมีการคอมไพล์ซ้ำ การประกาศจากภายนอกหมายความว่าไม่จำเป็นต้องคอมไพล์ซ้ำในกรณีที่มีการเปลี่ยนแปลงค่า แต่ในทางกลับกันทำให้ไม่สามารถใช้สวิตช์ได้ ข้อสรุปที่ถูกใช้สวิทช์จะเพิ่มการมีเพศสัมพันธ์ระหว่างรหัสสวิทช์และตัวแปรที่ใช้เป็นกรณี เมื่อมันตกลงแล้วใช้สวิตช์ เมื่อไม่เป็นเช่นนั้นก็ไม่แปลกใจ

.

แก้ไข 2013-01-15:

Vlad Lazarenkoแสดงความคิดเห็นกับคำตอบของฉันโดยให้ลิงก์ไปยังการศึกษาเชิงลึกของรหัสประกอบที่สร้างขึ้นโดยสวิตช์ ตรัสรู้มาก: http://lazarenko.me/switch/


การกำหนดค่าคงที่ให้เป็น enums ในไฟล์ส่วนหัวเป็นอีกวิธีหนึ่งในการจัดการสิ่งนี้
bk1e


1
@Vlad Lazarenko: ขอบคุณสำหรับลิงค์! มันเป็นการอ่านที่น่าสนใจมาก
paercebal

1
@AhmedHussein ลิงก์ของผู้ใช้ 404725 นั้นตาย โชคดีที่ฉันพบในเครื่อง Wayback: web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/... อันที่จริงเครื่อง WayBack สามารถเป็นพระพรได้
Jack Giffin

ขอบคุณมากมันมีประโยชน์มาก
Ahmed Hussein

20

คอมไพเลอร์จะปรับให้เหมาะสมแล้ว - ไปที่สวิตช์เนื่องจากเป็นสวิตช์ที่อ่านได้มากที่สุด


3
โอกาสที่คอมไพเลอร์นั้นจะไม่แตะต้องถ้าอย่างอื่น ในความเป็นจริงgccจะไม่ทำเช่นนั้นอย่างแน่นอน (มีเหตุผลที่ดีสำหรับสิ่งนั้น) เสียงดังกราวจะปรับให้เหมาะสมทั้งสองกรณีเป็นการค้นหาแบบไบนารี ยกตัวอย่างเช่นเห็นนี้

7

สวิตช์หากอ่านได้เท่านั้น ไจแอนต์ถ้างบยากที่จะรักษาและยากที่จะอ่านในความคิดของฉัน

ERROR_01 : // การตกลงมาโดยเจตนา

หรือ

(ERROR_01 == numError) ||

ข้อผิดพลาดในภายหลังมีแนวโน้มมากขึ้นและต้องการการพิมพ์และการจัดรูปแบบมากกว่าครั้งแรก


6

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


6

ใช้สวิตช์มันเป็นสิ่งที่มันมีไว้สำหรับและสิ่งที่โปรแกรมเมอร์คาดหวัง

ฉันจะใส่ป้ายชื่อเคสซ้ำซ้อน - เพียงเพื่อให้ผู้คนรู้สึกสะดวกสบายฉันพยายามจดจำว่าเมื่อไร / กฎใดที่ทำให้พวกเขาออกไป
คุณไม่ต้องการให้โปรแกรมเมอร์คนต่อไปทำงานโดยไม่จำเป็นต้องคิดอะไรเกี่ยวกับรายละเอียดของภาษา (อาจเป็นคุณในเวลาไม่กี่เดือน!)


4

switchคอมไพเลอร์เป็นสิ่งที่ดีจริงๆที่เพิ่มประสิทธิภาพ GCC ifล่าสุดยังเป็นสิ่งที่ดีในการเพิ่มประสิทธิภาพพวงของเงื่อนไขในการให้

ฉันทำกรณีทดสอบบางอย่างเกี่ยวกับgodbolt

เมื่อcaseค่าต่างๆถูกจัดกลุ่มใกล้กัน gcc, clang และ icc นั้นฉลาดพอที่จะใช้บิตแมปเพื่อตรวจสอบว่าค่าเป็นหนึ่งในค่าพิเศษหรือไม่

เช่น gcc 5.2 -O3 รวบรวมswitchถึง (และifสิ่งที่คล้ายกันมาก):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

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

gcc 4.9.2 -O3 คอมไพล์switchไปยังบิตแมป, แต่ทำได้1U<<errNumberด้วย mov / shift มันรวบรวมifรุ่นเพื่อซีรีส์ของสาขา

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

ให้สังเกตว่ามันลบ 1 จากerrNumber(ด้วยleaเพื่อรวมการดำเนินการนั้นกับการย้าย) ซึ่งช่วยให้บิตแมปเป็น 32 บิตได้ทันทีโดยหลีกเลี่ยง 64 บิตทันทีmovabsqซึ่งต้องใช้คำสั่งไบต์เพิ่มเติม

ลำดับที่สั้นลง (ในรหัสเครื่อง) จะเป็น:

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(ความล้มเหลวในการใช้jc fire_special_eventอยู่ทั่วไปทุกหนทุกแห่งและเป็นข้อผิดพลาดของคอมไพเลอร์ )

rep retถูกใช้ในเป้าหมายสาขาและสาขาที่มีเงื่อนไขเพื่อประโยชน์ของ AMD K8 และ K10 เก่า (pre-Bulldozer): `rep ret` หมายถึงอะไร . ถ้าไม่มีมันการคาดการณ์ของสาขาก็ไม่สามารถทำงานได้ดีกับซีพียูที่ล้าสมัยเหล่านั้น

bt(ทดสอบบิต) ด้วยการลงทะเบียนหาเรื่องรวดเร็ว มันรวมการทำงานของการเลื่อนซ้ายไปทีละ 1 errNumberบิตและการทำ a testแต่ยังคงมีความหน่วงแฝงอยู่ 1 รอบและมีเพียง Intel uop เดียว มันช้ากับ ARG หน่วยความจำเพราะความหมายของมันเกินไป CISC: ด้วยตัวถูกดำเนินการหน่วยความจำสำหรับ "บิตสตริง" ที่อยู่ของไบต์ที่จะทดสอบจะถูกคำนวณตาม ARG อื่น ๆ (หารด้วย 8) และ isn ไม่ จำกัด ก้อนที่ 1, 2, 4 หรือ 8byte ที่ชี้ไปยังตัวถูกดำเนินการโดยหน่วยความจำ

จากตารางคำสั่งของ Agner Fog คำสั่งการนับจำนวนตัวแปรจะช้ากว่าของbtIntel ล่าสุด (2 uops แทนที่จะเป็น 1 และ shift ไม่ได้ทำทุกอย่างที่จำเป็น)


4

ข้อดีของการสลับ () มากกว่าหากเป็นกรณีอื่น: - 1. สวิตช์มีประสิทธิภาพมากขึ้นเมื่อเทียบกับกรณีอื่นเนื่องจากแต่ละกรณีไม่ได้ขึ้นอยู่กับกรณีก่อนหน้าซึ่งแตกต่างจากกรณีอื่นที่ต้องการตรวจสอบคำสั่งแต่ละรายการสำหรับสภาพจริงหรือเท็จ

  1. เมื่อไม่มี ของค่าสำหรับนิพจน์เดียวสวิตช์ case นั้นยืดหยุ่นกว่าถ้าเป็นอย่างอื่นเพราะในกรณีอื่นการตัดสินกรณีจะยึดตามค่าสองค่าเท่านั้นนั่นคือจริงหรือเท็จ

  2. ค่าในสวิตช์ถูกกำหนดโดยผู้ใช้ในขณะที่ค่าในกรณีอื่นจะขึ้นอยู่กับข้อ จำกัด

  3. ในกรณีที่มีข้อผิดพลาดงบในสวิตช์สามารถข้ามการตรวจสอบและแก้ไขได้ง่ายซึ่งเป็นการยากที่จะตรวจสอบในกรณีที่มีคำสั่ง if if

  4. ตัวเรือนสวิทช์มีขนาดเล็กและอ่านและเข้าใจได้ง่ายขึ้น


2

IMO นี่เป็นตัวอย่างที่สมบูรณ์แบบของสิ่งที่สวิตช์ล้มลงถูกสร้างขึ้นมาเพื่อ


ใน c # นี่เป็นเพียงกรณีเดียวที่มีการคิดตก อาร์กิวเมนต์ที่ดีอยู่ที่นั่น
BCS

2

หากกรณีของคุณมีแนวโน้มที่จะยังคงจัดกลุ่มในอนาคต - หากมีมากกว่าหนึ่งกรณีตรงกับผลลัพธ์เดียว - สวิตช์อาจพิสูจน์ให้อ่านและบำรุงรักษาได้ง่ายขึ้น


2

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

ฉันชอบถ้าคำสั่งมากกว่าคำสั่งกรณีเพราะพวกเขาสามารถอ่านได้และมีความยืดหยุ่นมากขึ้น - คุณสามารถเพิ่มเงื่อนไขอื่น ๆ ที่ไม่ได้ขึ้นอยู่กับความเท่าเทียมกันของตัวเลขเช่น "|| max <min" แต่สำหรับกรณีง่าย ๆ ที่คุณโพสต์ไว้ที่นี่ไม่สำคัญเลยเพียงทำสิ่งที่คุณอ่านได้มากที่สุด


2

สวิตช์เป็นที่ต้องการอย่างแน่นอน มันง่ายกว่าที่จะดูรายการของสวิตช์และรู้ว่ามันทำอะไรได้มากกว่าอ่านเงื่อนไขที่ยาว

การทำซ้ำในifสภาพยากต่อสายตา หนึ่งสมมติว่าของ==ถูกเขียน!=; คุณจะสังเกตเห็นไหม หรือถ้าอินสแตนซ์หนึ่งของ 'numError' ถูกเขียน 'nmuError' ซึ่งเพิ่งจะคอมไพล์แล้ว

โดยทั่วไปฉันต้องการใช้รูปแบบที่หลากหลายแทนสวิตช์ แต่ถ้าไม่มีรายละเอียดเพิ่มเติมเกี่ยวกับบริบทมันยากที่จะพูด

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


2

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


2
ฉันไม่เห็นด้วยด้วยเหตุผลเดียวกับที่ฉันไม่เห็นด้วยกับกรณีที่ตก ฉันอ่านสวิตช์เป็น "ในกรณี 01,07,0A, 10,15,16 และ 20 ไฟเหตุการณ์พิเศษ" ไม่มีการตกส่วนอื่น ๆ , นี่เป็นเพียงส่วนหนึ่งของไวยากรณ์ C ++ ที่คุณทำซ้ำคำสำคัญ 'กรณี' สำหรับแต่ละค่า
MSalters

1

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


1

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

switch(numerror){
    ERROR_20 : { fire_special_event(); } break;
    default : { null; } break;
}

บางทีอาจจะทดสอบสภาพของคุณ (ในกรณีนี้ numerror) กับรายการของความเป็นไปได้อาร์เรย์อาจจะทำให้สวิทช์ของคุณไม่ได้ใช้จนกว่าจะมีผลลัพธ์ที่แน่นอน


มีข้อผิดพลาดทั้งหมดประมาณ 30 ข้อ 10 จำเป็นต้องมีการดำเนินการพิเศษดังนั้นฉันจึงใช้ค่าเริ่มต้นสำหรับข้อผิดพลาด ~ 20 ที่ไม่ต้องการการดำเนินการ ...
Zing-

1

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


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

ตารางมีความซับซ้อนน้อยมาก - อันที่จริงแล้วมันอาจจะง่ายกว่าการเปลี่ยนเป็นรหัส และคำสั่งไม่พูดถึงประสิทธิภาพเป็นปัจจัย
Greg Whitfield

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

นอกจากนี้ถ้าคุณกำลังจะไปดำเนินการอะไรด้วยตัวเองให้แล้วstd::bitset<MAXERR> specialerror; if (specialerror[err]) { special_handler(); }นี่จะเร็วกว่าตารางกระโดด ในกรณีที่ไม่ได้ดำเนินการ
ปีเตอร์กอร์เดส

1

ฉันไม่แน่ใจเกี่ยวกับแนวปฏิบัติที่ดี แต่ฉันจะใช้สวิตช์ - จากนั้นจึงดักจับการตกหล่นโดยเจตนาผ่าน 'ค่าเริ่มต้น'


1

ฉันมักจะชอบวิธีนี้มาก

unsigned int special_events[] = {
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 };
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) {
     for (int i = 0; i < special_events_length; i++) {
         if (numError == special_events[i]) {
             fire_special_event();
             break;
          }
     }
  }

ทำข้อมูลให้ฉลาดขึ้นเล็กน้อยเพื่อที่เราจะได้ทำให้ตรรกะนั้นโง่ไปหน่อย

ฉันรู้ว่ามันดูแปลก ๆ นี่คือแรงบันดาลใจ (จากวิธีที่ฉันทำใน Python):

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()

4
ไวยากรณ์ของภาษาไม่มีผลกระทบต่อวิธีการที่เราใช้วิธีการแก้ปัญหา ... => มันดูน่าเกลียดใน C และมีความสุขในหลาม :)
rlerallut

ใช้บิตแมป? หาก error_0a เป็น 0x0a ฯลฯ คุณสามารถกำหนดให้เป็นบิตได้นาน long long special_events = 1LL << 1 | 1LL << 7 | 1LL << 0xa ... จากนั้นใช้ if (special_events & (1LL << numError) fire_special_event ()
paperhorse

1
yuck คุณเปลี่ยน O (1) การดำเนินการกรณีที่เลวร้ายที่สุด (ถ้าสร้างตารางกระโดด) เป็น O (N) กรณีที่เลวร้ายที่สุด (โดยที่ N คือจำนวนกรณีที่จัดการ) และคุณใช้breakนอกcase(ใช่รองลงมา บาป แต่เป็นบาปอย่างไรก็ตาม) :)
Mac

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

1
while (true) != while (loop)

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


นี่เป็นความเห็นต่อคำตอบของ McAnix นั่นเป็นเพียงหนึ่งในปัญหาที่เกิดขึ้นกับความพยายามในการกำหนดเวลาifเทียบกับสภาswitchพลูปสิ้นสุดใน Java
ปีเตอร์กอร์เดส

1

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

อนุญาตให้คุณทดสอบตัวแปรตามช่วงที่ระบุคุณสามารถใช้ฟังก์ชัน (ไลบรารีมาตรฐานหรือส่วนบุคคล) เป็นเงื่อนไขได้

(ตัวอย่าง:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   {
     cout<<"a is between 0, 5\n";

   }else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   }else{

       "a is not an integer, or is not in range 0,10\n";

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

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 {
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

ฉันต้องการถ้า - อื่นถ้า - งบอื่น แต่มันขึ้นอยู่กับคุณ หากคุณต้องการใช้ฟังก์ชั่นเป็นเงื่อนไขหรือคุณต้องการทดสอบบางอย่างกับช่วงอาร์เรย์หรือเวกเตอร์และ / หรือคุณไม่สนใจเกี่ยวกับการซ้อนที่ซับซ้อนฉันขอแนะนำให้ใช้ If else ถ้าบล็อกอื่น หากคุณต้องการทดสอบกับค่าเดียวหรือคุณต้องการบล็อกที่สะอาดและอ่านง่ายฉันขอแนะนำให้คุณใช้ตัวบล็อกสลับ ()


0

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


0

ฉันรู้ว่ามันเก่า แต่

public class SwitchTest {
static final int max = 100000;

public static void main(String[] args) {

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) {
  if (counter1 == max) {
    break;
  } else {
    counter1++;
  }
}
total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) {
  switch (counter2) {
    case max:
      loop = false;
      break;
    default:
      counter2++;
  }
}
total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);
}
}

การเปลี่ยนแปลงจำนวนลูปเปลี่ยนแปลงไปมาก:

ในขณะที่ if / else: 5ms Switch: 1ms Max Loops: 100000

ในขณะที่ if / else: 5ms Switch: 3ms Loops สูงสุด: 1000000

ในขณะที่ if / else: 5ms Switch: 14ms Loops สูงสุด: 10,000000

ในขณะที่ if / else: 5ms Switch: 149ms Max Loops: 100000000

(เพิ่มคำสั่งเพิ่มเติมถ้าคุณต้องการ)


3
จุดดี แต่ sry เพื่อนคุณอยู่ในภาษาที่ผิด การเปลี่ยนภาษาเปลี่ยนแปลงไปมาก;)
Gabriel Schreiber

2
การif(max) breakวนซ้ำทำงานในเวลาคงที่โดยไม่คำนึงถึงการนับลูป? เสียงเหมือนคอมไพเลอร์ JIT นั้นฉลาดพอที่จะปรับลูปให้counter2=maxเหมาะสม และอาจช้ากว่าสวิตช์ถ้าการโทรครั้งแรกcurrentTimeMillisมีค่าใช้จ่ายมากขึ้นเพราะยังไม่ได้รวบรวมทุกอย่างของ JIT การใส่ลูปในลำดับอื่นอาจทำให้ได้ผลลัพธ์ที่แตกต่างกัน
ปีเตอร์กอร์เดส
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.