โปรโมชั่น C Integer บน MCU 8 บิต


14

การใช้ avr-gcc เป็นตัวอย่างจะระบุชนิด int ให้มีความกว้าง 16 บิต การดำเนินการกับตัวถูกดำเนินการ 8 บิตใน C ส่งผลให้ตัวถูกดำเนินการถูกแปลงเป็นชนิด int 16 บิตเนื่องจากการส่งเสริมจำนวนเต็มใน C นี่หมายความว่าการดำเนินการทางคณิตศาสตร์ 8 บิตทั้งหมดบน AVR จะใช้เวลานานกว่านี้หากเขียนใน C มากกว่า ถ้าเขียนในการชุมนุมเนื่องจากการส่งเสริมจำนวนเต็มของ C?


1
ฉันไม่คิดอย่างนั้นคอมไพเลอร์จะรับรู้ว่าตัวแปรปลายทางเป็นอักขระที่ไม่ได้ลงชื่อดังนั้นจึงไม่ต้องกังวลในการคำนวณบิต 8 อันดับแรก ถึงกระนั้นฉันก็พบว่า GCC บางครั้งไม่ดีในการเพิ่มประสิทธิภาพรหัสดังนั้นถ้าคุณรหัสใน ASM ผล MGIHT จะเร็วขึ้น อย่างไรก็ตามหากคุณกำลังทำงาน / ช่วงเวลาที่สำคัญมากโดยมีข้อ จำกัด ด้านงบประมาณที่เข้มงวดมากคุณควรเลือกโปรเซสเซอร์ที่มีประสิทธิภาพมากขึ้นและโปรแกรมใน C หรือไม่ต้องกังวลกับประสิทธิภาพที่ลดลง - สู่ตลาด, การอ่านโค้ดที่ดีขึ้น / สามารถใช้ซ้ำได้, มีข้อบกพร่องน้อยลง, ฯลฯ .. )
next-hack

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

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

3
ข้อควรจำ: หลีกเลี่ยงการตอบคำถามในความคิดเห็น
ท่อ

4
คำถามประเภทนี้ดีกว่าที่จะถามผู้เชี่ยวชาญ C ที่ SO เนื่องจากเป็นคำถามซอฟต์แวร์บริสุทธิ์ โปรโมชั่นจำนวนเต็มใน C เป็นหัวข้อที่ค่อนข้างซับซ้อน - โปรแกรมเมอร์ C โดยเฉลี่ยจะมีความเข้าใจผิดมากมายเกี่ยวกับเรื่องนี้
Lundin

คำตอบ:


16

เรื่องสั้นสั้น:

การส่งเสริมการขายจำนวนเต็มถึง 16 บิตจะเกิดขึ้นเสมอ - มาตรฐาน C บังคับใช้สิ่งนี้ แต่คอมไพเลอร์ได้รับอนุญาตให้เพิ่มประสิทธิภาพการคำนวณกลับลงไปที่ 8 บิต (คอมไพเลอร์ระบบฝังตัวจะค่อนข้างดีในการเพิ่มประสิทธิภาพดังกล่าว) ถ้ามันสามารถอนุมานได้ว่าสัญญาณจะเหมือนกันถ้ามันได้รับการส่งเสริมประเภท

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

คำอธิบายรายละเอียดสามารถพบได้ที่นี่: ประเภทโดยปริยายกฎโปรโมชั่น


8
unsigned int fun1 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned char fun2 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned int fun3 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

unsigned char fun4 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

อย่างที่คาดไว้ fun1 นั้นเป็น ints ทั้งหมดดังนั้น 16 บิตคณิตศาสตร์

00000000 <fun1>:
   0:   86 0f           add r24, r22
   2:   97 1f           adc r25, r23
   4:   08 95           ret

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

00000006 <fun2>:
   6:   86 0f           add r24, r22
   8:   08 95           ret

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

0000000a <fun3>:
   a:   70 e0           ldi r23, 0x00   ; 0
   c:   26 2f           mov r18, r22
   e:   37 2f           mov r19, r23
  10:   28 0f           add r18, r24
  12:   31 1d           adc r19, r1
  14:   82 2f           mov r24, r18
  16:   93 2f           mov r25, r19
  18:   08 95           ret

และอุดมคติฉันรู้ว่ามันคือ 8 บิตต้องการผลลัพธ์ 8 บิตดังนั้นฉันก็บอกให้ทำ 8 บิตตลอดทาง

0000001a <fun4>:
  1a:   86 0f           add r24, r22
  1c:   08 95           ret

ดังนั้นโดยทั่วไปจะดีกว่าที่จะตั้งเป้าหมายขนาด register ซึ่งเป็นขนาดที่เหมาะสม (u) int สำหรับ 8 บิต mcu เช่นนี้ผู้เขียนคอมไพเลอร์ต้องประนีประนอม ... จุดที่ไม่ทำให้เป็นนิสัย การใช้ uchar สำหรับคณิตศาสตร์ที่คุณรู้ว่าไม่จำเป็นต้องมากกว่า 8 บิตเหมือนเมื่อคุณย้ายรหัสนั้นหรือเขียนรหัสใหม่เช่นนั้นบนโปรเซสเซอร์ที่มีการลงทะเบียนมากขึ้นตอนนี้คอมไพเลอร์ต้องเริ่มปิดบังและขยายเครื่องหมายซึ่งบางคำสั่ง และคนอื่น ๆ ไม่

00000000 <fun1>:
   0:   e0800001    add r0, r0, r1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e0800001    add r0, r0, r1
   c:   e20000ff    and r0, r0, #255    ; 0xff
  10:   e12fff1e    bx  lr

บังคับให้ 8 บิตมีค่าใช้จ่ายมากขึ้น ฉันโกงเล็ก ๆ น้อย ๆ / ต้องการตัวอย่างที่ซับซ้อนมากขึ้นเล็กน้อยเพื่อดูสิ่งนี้มากขึ้นอย่างเป็นธรรม

แก้ไขตามการอภิปรายแสดงความคิดเห็น

unsigned int fun ( unsigned char a, unsigned char b )
{
    unsigned int c;
    c = (a<<8)|b;
    return(c);
}

00000000 <fun>:
   0:   70 e0           ldi r23, 0x00   ; 0
   2:   26 2f           mov r18, r22
   4:   37 2f           mov r19, r23
   6:   38 2b           or  r19, r24
   8:   82 2f           mov r24, r18
   a:   93 2f           mov r25, r19
   c:   08 95           ret

00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

ไม่แปลกใจ แม้ว่าทำไมออพติไมเซอร์ถึงออกคำสั่งพิเศษนั้น แต่คุณไม่สามารถใช้ ldi บน r19 ได้หรือไม่? (ฉันรู้คำตอบเมื่อฉันถาม)

EDIT2

สำหรับ avr

avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

เพื่อหลีกเลี่ยงนิสัยที่ไม่ดีหรือไม่เปรียบเทียบ 8 บิต

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

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

whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o

และใช่การใช้ไบต์สำหรับตัวแปรขนาดไบต์แน่นอนบน avr, pic ฯลฯ จะช่วยให้คุณประหยัดหน่วยความจำและคุณต้องการที่จะพยายามอนุรักษ์มันจริงๆ ... ถ้าคุณใช้มันจริง ๆ แต่เท่าที่จะทำได้ จะอยู่ในหน่วยความจำมากที่สุดเท่าที่จะเป็นไปได้ดังนั้นการประหยัดแฟลชจึงมาจากการไม่มีตัวแปรเพิ่มเติมการประหยัด RAM อาจหรืออาจไม่จริง ..


2
"คอมไพเลอร์ไม่เคยทำแบบนี้ไม่แน่ใจว่าเวอร์ชั่นไหนทำให้เกิดการเริ่มต้นขึ้นวิ่งเข้ามาในช่วงต้น ๆ ในอาชีพของฉันและแม้คอมไพเลอร์ที่โปรโมตไม่เป็นระเบียบ (เหมือนตอนบน) ทำโปรโมตแม้ว่าฉันจะบอกว่า ไม่แปลกใจ " เป็นเพราะคอมไพล์เลอร์ C ที่เคยมีความสอดคล้องตามมาตรฐานที่น่ากลัว :) คอมไพเลอร์ได้รับอนุญาตให้ทำการออปติไมซ์ แต่ที่นี่มันไม่สามารถหักได้ว่าผลลัพธ์จะพอดีกับunsigned charมันดังนั้นมันจึงต้องทำการเลื่อนขั้นเป็น 16 บิตตามต้องการ ตามมาตรฐาน
Lundin

1
@old_timer (a<<8)|bผิดเสมอสำหรับระบบใด ๆ ที่intมี 16 บิต aจะได้รับการส่งเสริมโดยนัยintซึ่งลงนาม ในกรณีที่aเก็บค่าไว้ใน MSB คุณจะเปลี่ยนข้อมูลนั้นเป็นบิตสัญญาณของตัวเลข 16 บิตซึ่งเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
Lundin

1
fun3 is fun..ny ... unoptimized ทั้งหมดโดยคอมไพเลอร์ ... พิจารณาว่า r1 เป็น 0 เสมอใน GCC และระบุ ra, rb, {rh, rl} การลงทะเบียนตัวแปร a, b และผลลัพธ์ คอมไพเลอร์สามารถทำได้: 1) mov rh, r1; 2) mov rl, ra; 2) เพิ่ม rl, rb; 3) adc rh, rh; 4) เกษียณ 4 คำแนะนำ, vs 7 หรือ 8 ... คำสั่ง 1 สามารถเปลี่ยนแปลงได้ใน ldi rh, 0
next-hack

1
นี่จะเป็นคำตอบที่ดีกว่าหากระบุคอมไพเลอร์และตัวเลือกที่เกี่ยวข้องในการใช้งาน
Russell Borogove

1
เป็นความคิดที่ดีที่จะหลีกเลี่ยงการใช้ int / char เป็นต้นและควรใช้ int16_t และ int8_t ที่ชัดเจนและอ่านได้มากกว่า
ผู้ใช้

7

ไม่จำเป็นเนื่องจากคอมไพเลอร์สมัยใหม่ทำงานได้ดีในการเพิ่มประสิทธิภาพโค้ดที่สร้างขึ้น ตัวอย่างเช่นถ้าคุณเขียนz = x + y;ว่าตัวแปรทั้งหมดอยู่ที่unsigned charใดคอมไพเลอร์จำเป็นต้องเลื่อนขั้นไปunsigned intก่อนทำการคำนวณ อย่างไรก็ตามเนื่องจากผลลัพธ์จะเหมือนกันทุกประการหากไม่มีการเลื่อนระดับคอมไพเลอร์จะสร้างโค้ดซึ่งเพิ่งเพิ่มตัวแปร 8 บิต

แน่นอนว่านี่อาจไม่ใช่กรณีเสมอไปตัวอย่างเช่นผลลัพธ์ของการz = (x + y)/2;ขึ้นอยู่กับไบต์บนดังนั้นการเลื่อนตำแหน่งจะเกิดขึ้น ก็ยังสามารถหลีกเลี่ยงได้โดยไม่ต้อง resorting unsigned charการชุมนุมโดยการหล่อผลกลับกลาง

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


4
"คอมไพเลอร์จำเป็นต้องเลื่อนระดับพวกเขาไปยัง int ที่ไม่ได้ลงชื่อ" ไม่จำเป็นต้องใช้คอมไพเลอร์เพื่อโปรโมตโฆษณาintเพราะcharจะไม่มีอันดับการแปลงเหมือนกับintแพลตฟอร์มใด ๆ
Lundin

3
"ตัวอย่างเช่นคอมไพเลอร์ 8 บิตจำนวนมากมี pragma หรือสวิตช์บรรทัดคำสั่งเพื่อให้พอดีกับประเภทการแจงนับ 1 ไบต์แทนที่จะเป็น int ตามที่ต้องการโดย C" มาตรฐาน C อนุญาตให้ใช้ตัวแปรการแจงนับได้ 1 ไบต์ เพียงต้องการให้ค่าคงที่การแจงนับต้องเป็นint(ใช่มันไม่สอดคล้องกัน) C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...
Lundin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.