ความแตกต่างของประสิทธิภาพระหว่างจำนวนเต็มที่ไม่ได้ลงนามและที่เซ็นชื่อมีอะไรบ้าง [ปิด]


42

ฉันตระหนักถึงประสิทธิภาพในการทำงานเมื่อผสม ints ที่เซ็นชื่อกับลอย

มันแย่กว่าไหมถ้าจะผสม ints ที่ไม่ได้ลงนามกับ Floats?

มีการเข้าชมใด ๆ เมื่อผสมการเซ็นชื่อ / ไม่ได้ลงชื่อโดยไม่มีการลอยหรือไม่?

ขนาดแตกต่างกัน (u32, u16, u8, i32, i16, i8) มีผลกระทบต่อประสิทธิภาพหรือไม่? บนแพลตฟอร์มใด


2
ฉันได้ลบข้อความ / แท็กเฉพาะของ PS3 ออกเพราะนี่เป็นคำถามที่ดีเกี่ยวกับสถาปัตยกรรมใด ๆ และคำตอบก็เป็นจริงสำหรับสถาปัตยกรรมทั้งหมดที่แยกการลงทะเบียนจำนวนเต็มและทศนิยมซึ่งเป็นจริงทั้งหมด

คำตอบ:


36

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

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


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

4
@bummzack: ใช้ได้กับ SPE เท่านั้นไม่ใช่ PPE SPE นั้นมีสภาพแวดล้อมที่มีจุดลอยตัวเป็นพิเศษเอ่อพิเศษและการคัดเลือกนักแสดงนั้นก็ค่อนข้างแพง นอกจากนี้ค่าใช้จ่ายยังคงเหมือนเดิมสำหรับจำนวนเต็มเมื่อเทียบกับที่ไม่ได้ลงชื่อ

นั่นเป็นบทความที่ดีและเป็นสิ่งสำคัญที่ต้องรู้เกี่ยวกับ LHS (และฉันลงคะแนนให้) แต่คำถามของฉันเกี่ยวกับบทลงโทษที่เกี่ยวข้องกับเครื่องหมายเหล่านั้น ฉันรู้ว่าสิ่งเหล่านี้มีขนาดเล็กและอาจเล็กน้อย แต่ฉันยังต้องการเห็นตัวเลขจริงหรือข้อมูลอ้างอิงเกี่ยวกับพวกเขา
Luis

1
@Luis - ฉันพยายามค้นหาเอกสารสาธารณะเกี่ยวกับเรื่องนี้ แต่ไม่สามารถหาได้ในขณะนี้ หากคุณสามารถเข้าถึงเอกสาร Xbox360 มีเอกสารทางเทคนิคที่ดีโดย Bruce Dawson ที่ครอบคลุมเนื้อหาบางส่วนนี้ (และเป็นเอกสารที่ดีโดยทั่วไป)
celion

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

12

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

ก่อนอื่นเรามาดูว่าต้นทุนการขยับขยายที่ไม่ได้ลงนามคืออะไร:

unsigned char x = 1;
unsigned int y = 1;
unsigned int z;
z = x;
z = y;

ส่วนที่เกี่ยวข้องแยกส่วนออกเป็น (โดยใช้ GCC 4.4.5):

    z = x;
  27:   0f b6 45 ff             movzbl -0x1(%ebp),%eax
  2b:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  2e:   8b 45 f8                mov    -0x8(%ebp),%eax
  31:   89 45 f4                mov    %eax,-0xc(%ebp)

โดยพื้นฐานแล้วเหมือนกัน - ในกรณีหนึ่งเราย้ายไบต์ในอีกกรณีหนึ่งเราเลื่อนคำ ต่อไป:

signed char x = 1;
signed int y = 1;
signed int z;
z = x;
z = y;

กลายเป็น:

   z = x;
  11:   0f be 45 ff             movsbl -0x1(%ebp),%eax
  15:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  18:   8b 45 f8                mov    -0x8(%ebp),%eax
  1b:   89 45 f4                mov    %eax,-0xc(%ebp)

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

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

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


2
(CW เพราะนี่เป็นเพียงความคิดเห็นเกี่ยวกับคำตอบของ celion และเพราะฉันอยากรู้ว่าการแก้ไขโค้ดใดที่ผู้คนอาจต้องทำให้เป็นตัวอย่างเพิ่มเติม)

ข้อมูลเกี่ยวกับ PS3 CPU พร้อมใช้งานและถูกต้องตามกฎหมายดังนั้นการสนทนาเกี่ยวกับสิ่งต่างๆเกี่ยวกับ CPU ของ PS3 จึงไม่ใช่ปัญหา จนกระทั่ง Sony ลบการสนับสนุน OtherOS ทุกคนสามารถติด Linux บน PS3 และตั้งโปรแกรมได้ GPU มีขีด จำกัด แต่ CPU (รวมถึง SPE) นั้นใช้ได้ แม้จะไม่มีการสนับสนุน OtherOS คุณก็สามารถคว้า GCC ที่เหมาะสมและดูว่ารหัสเป็นอย่างไร
JasonD

@ Jason: ฉันตั้งค่าสถานะโพสต์ของฉันเป็น CW ดังนั้นหากมีคนทำสิ่งนี้พวกเขาสามารถให้ข้อมูลได้ อย่างไรก็ตามทุกคนที่เข้าถึง GameOS อย่างเป็นทางการของ Sony - ซึ่งเป็นคนเดียวที่สำคัญ - อาจถูกกันไม่ให้ทำเช่นนั้น

อันที่จริงจำนวนเต็มที่ลงนามแล้วนั้นแพงกว่าใน PPC IIRC มันมีผลงานเล็กน้อย แต่มี ... รายละเอียด PS3 PPU / SPU มากมายอยู่ที่นี่: jheriko-rtw.blogspot.co.uk/2011/07/ps3-ppuspu-docs.htmlและ ที่นี่: jheriko-rtw.blogspot.co.uk/2011/03/ppc-instruction-set.html สงสัยว่าคอมไพเลอร์ GameOS นี้คืออะไร? นั่นคือคอมไพเลอร์ GCC หรือหนึ่งใน SNC หรือไม่ iirc นอกเหนือจากที่กล่าวมาแล้วการเปรียบเทียบที่เซ็นชื่อมีค่าใช้จ่ายเมื่อพูดถึงการปรับลูปในสุดให้ดีที่สุด ฉันไม่สามารถเข้าถึงเอกสารที่อธิบายเรื่องนี้ได้ - และแม้ว่าฉันจะ ...
jheriko

4

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

unsigned foo(unsigned a) { return a / 1024U; }

จะได้รับการปรับให้เหมาะสมกับ:

unsigned foo(unsigned a) { return a >> 10; }

แต่...

int foo(int a) { return a / 1024; }

จะปรับให้เหมาะกับ:

int foo(int a) {
  return (a + 1023 * (a < 0)) >> 10;
}

หรือในระบบที่การแตกกิ่งมีราคาถูก

int foo(int a) {
  if (a >= 0) return a >> 10;
  else return (a + 1023) >> 10;
}

กันไปสำหรับ modulo สิ่งนี้ถือเป็นจริงสำหรับ non-powers-of-2 (แต่ตัวอย่างมีความซับซ้อนมากขึ้น) หากสถาปัตยกรรมของคุณไม่มีการแบ่งฮาร์ดแวร์ (เช่น ARM ส่วนใหญ่) การแบ่ง non-consts ที่ไม่ได้ลงนามก็จะเร็วขึ้นเช่นกัน

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

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


ฉันแก้ไขโค้ดที่ได้รับการปรับปรุงของคุณให้มีความสะท้อนมากกว่าสิ่งที่ GCC สร้างขึ้นจริงแม้แต่ใน -O0 มีสาขาทำให้เข้าใจผิดเมื่อทดสอบ + ทุ่งหญ้าช่วยให้คุณทำมันได้สาขา

2
ใน x86 อาจจะ บน ARMv7 มันจะทำงานแบบมีเงื่อนไข
John Ripley

3

การดำเนินการกับ int ที่ลงนามหรือไม่ได้ลงนามมีค่าใช้จ่ายเท่ากันกับโปรเซสเซอร์ปัจจุบัน (x86_64, x86, powerpc, arm) บนตัวประมวลผล 32 บิต u32, u16, u8 s32, s16, s8 ควรเหมือนกัน คุณสามารถลงโทษได้โดยมีการปรับตำแหน่งที่ไม่ดี

แต่แปลง int เป็น float หรือ float เป็น int เป็นการดำเนินการที่มีค่าใช้จ่ายสูง คุณสามารถค้นหาการปรับใช้ที่ปรับให้เหมาะสมได้อย่างง่ายดาย (SSE2, Neon ... )

จุดที่สำคัญที่สุดคือการเข้าถึงหน่วยความจำ หากข้อมูลของคุณไม่พอดีกับแคช L1 / L2 คุณจะเสียวงจรมากกว่าการแปลง


2

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

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

ระมัดระวังเกี่ยวกับการจัดตำแหน่งข้อมูล ในการใช้งาน HW ส่วนใหญ่โหลดที่ไม่ได้แนวและร้านค้าจะช้ากว่า การจัดตำแหน่งตามธรรมชาติหมายความว่าสำหรับการพูดคำ 4byte ที่อยู่คือหลายสี่และที่อยู่แปดคำไบต์ควรจะทวีคูณของแปดไบต์ สิ่งนี้นำไปสู่ ​​SSE (128 บิตสนับสนุนการจัดตำแหน่ง 16byte) AVX เร็ว ๆ นี้จะขยายขนาดการลงทะเบียน "vector" เหล่านี้เป็น 256bits และ 512bits และโหลด / สโตร์ที่จัดเรียงจะเร็วกว่าอันที่ไม่จัดแนว สำหรับ HW geeks การดำเนินการของหน่วยความจำที่ไม่ได้จัดแนวอาจครอบคลุมสิ่งต่าง ๆ เช่น cacheline และแม้แต่ขอบเขตของหน้าซึ่ง HW ต้องระวัง


1

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

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

ติดกับ int สำหรับตัวแปรท้องถิ่นและส่วนใหญ่จะเกิดขึ้นตามค่าเริ่มต้น


0

ดังที่ celion ชี้ให้เห็นว่าค่าใช้จ่ายในการแปลงระหว่าง ints และ float นั้นส่วนใหญ่เกี่ยวข้องกับการคัดลอกและการแปลงค่าระหว่างรีจิสเตอร์ โอเวอร์เฮดเพียงตัวเดียวของ ints ที่ไม่ได้รับการลงนามในและของตัวเองนั้นมาจากพฤติกรรมการรับประกันแบบห่อหุ้มซึ่งรับประกันการตรวจสอบโอเวอร์โฟลจำนวนหนึ่งในโค้ดที่คอมไพล์

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

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


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

@JoeWreschnig: ประณาม ฉันดูเหมือนจะไม่พบมัน แต่ฉันรู้ว่าฉันเคยเห็นตัวอย่างของแอสเซมบลีเอาท์พุทแอสเซมเบลอร์ที่แตกต่างกันสำหรับการทำงานแบบวิจิตรที่กำหนดไว้อย่างน้อยในบางแพลตฟอร์ม โพสต์ที่เกี่ยวข้องเท่านั้นที่ฉันสามารถหาได้: stackoverflow.com/questions/4712315/…
Jon Purdy

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