การคำนวณจุดลอยตัวเทียบกับจำนวนเต็มบนฮาร์ดแวร์สมัยใหม่


100

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

ตอนนี้ฉันจำได้ว่าอ่านเกี่ยวกับวิธีการคำนวณจุดลอยตัวช้ามากประมาณ 386 วันซึ่งฉันเชื่อว่า (IIRC) มีตัวเลือกร่วมที่เป็นตัวเลือก แต่ในปัจจุบันด้วยซีพียูที่ซับซ้อนและทรงพลังมากขึ้นอย่างทวีคูณทำให้ "ความเร็ว" ไม่แตกต่างกันหากทำการคำนวณทศนิยมหรือจำนวนเต็ม? โดยเฉพาะอย่างยิ่งเนื่องจากเวลาในการคำนวณจริงนั้นมีขนาดเล็กเมื่อเทียบกับบางสิ่งเช่นการทำให้ท่อตันหรือดึงข้อมูลจากหน่วยความจำหลัก?

ฉันรู้ว่าคำตอบที่ถูกต้องคือการเปรียบเทียบกับฮาร์ดแวร์เป้าหมายวิธีที่ดีในการทดสอบสิ่งนี้คืออะไร? ฉันเขียนโปรแกรม C ++ เล็ก ๆ สองโปรแกรมและเปรียบเทียบเวลาทำงานกับ "เวลา" บน Linux แต่เวลาทำงานจริงนั้นแปรปรวนเกินไป (ไม่ได้ช่วยให้ฉันทำงานบนเซิร์ฟเวอร์เสมือน) การใช้เวลาทั้งวันของฉันไม่เพียงพอกับการใช้เกณฑ์มาตรฐานหลายร้อยรายการการสร้างกราฟ ฯลฯ มีสิ่งใดบ้างที่ฉันสามารถทำได้เพื่อทดสอบความเร็วสัมพัทธ์ที่สมเหตุสมผล ความคิดหรือความคิด? ฉันผิดเต็ม ๆ เหรอ?

โปรแกรมที่ฉันใช้ดังต่อไปนี้ไม่เหมือนกันโดยวิธีใด ๆ :

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{
    int accum = 0;

    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += rand( ) % 365;
    }
    std::cout << accum << std::endl;

    return 0;
}

โปรแกรม 2:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{

    float accum = 0;
    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += (float)( rand( ) % 365 );
    }
    std::cout << accum << std::endl;

    return 0;
}

ขอบคุณล่วงหน้า!

แก้ไข: แพลตฟอร์มที่ฉันสนใจคือ x86 หรือ x86-64 ปกติที่ทำงานบนเครื่อง Linux และ Windows บนเดสก์ท็อป

แก้ไข 2 (วางจากความคิดเห็นด้านล่าง): ขณะนี้เรามีฐานรหัสที่ครอบคลุม จริงๆแล้วฉันได้คิดสรุปว่าเรา "ต้องไม่ใช้ float เนื่องจากการคำนวณจำนวนเต็มเร็วกว่า" - และฉันกำลังมองหาวิธี (ถ้าเป็นจริง) เพื่อหักล้างข้อสันนิษฐานทั่วไปนี้ ฉันตระหนักดีว่ามันคงเป็นไปไม่ได้ที่จะทำนายผลลัพธ์ที่แน่นอนสำหรับเราโดยไม่ต้องทำงานทั้งหมดและจัดทำเป็นโปรไฟล์ในภายหลัง

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


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

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

3
ฉันเชื่อว่าฮาร์ดแวร์ใน HTC Hero (android) ของฉันไม่มี FPU แต่ฮาร์ดแวร์ใน Google NexusOne (android) มี เป้าหมายของคุณคืออะไร? พีซีเดสก์ท็อป / เซิร์ฟเวอร์? netbooks (แขนที่เป็นไปได้ + linux)? โทรศัพท์?
SteelBytes

5
หากคุณต้องการ FP ที่รวดเร็วบน x86 ให้ลองคอมไพล์ด้วยการเพิ่มประสิทธิภาพและการสร้างโค้ด SSE SSE (ไม่ว่าจะเป็นเวอร์ชันใดก็ตาม) อย่างน้อยก็สามารถเพิ่มลบและคูณจำนวนลอยในรอบเดียวได้ Divide, พอควรและฟังก์ชั่นที่สูงขึ้นจะเสมอจะช้า นอกจากนี้โปรดทราบว่าfloatได้รับการเพิ่มความเร็ว แต่โดยปกติdoubleจะไม่
Mike D.

1
จำนวนเต็มจุดคงที่จะประมาณ FP โดยใช้การดำเนินการจำนวนเต็มหลายรายการเพื่อป้องกันไม่ให้ผลลัพธ์ล้น ซึ่งเกือบจะช้ากว่าการใช้ FPU ที่มีความสามารถสูงที่พบในซีพียูเดสก์ท็อปสมัยใหม่เกือบตลอดเวลา เช่น MAD ตัวถอดรหัส mp3 แบบจุดคงที่ช้ากว่า libmpg123 และแม้ว่าจะมีคุณภาพดีสำหรับตัวถอดรหัสจุดคงที่ แต่ libmpg123 ก็ยังมีข้อผิดพลาดในการปัดเศษน้อยกว่า wezm.net/technical/2008/04/mp3-decoder-libraries-เปรียบเทียบสำหรับการวัดประสิทธิภาพบน PPC G5
Peter Cordes

คำตอบ:


35

อนิจจาฉันสามารถให้คำตอบที่ "มันขึ้นอยู่กับ" ...

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

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

ฉันคิดว่าคุณกำลังถามคำถามนี้เนื่องจากคุณกำลังทำงานกับแอปพลิเคชันที่มีความสำคัญด้านประสิทธิภาพ หากคุณกำลังพัฒนาสำหรับสถาปัตยกรรม x86 และคุณต้องการประสิทธิภาพเพิ่มเติมคุณอาจต้องการพิจารณาการใช้ส่วนขยาย SSE สิ่งนี้สามารถเพิ่มความเร็วในการคำนวณเลขคณิตทศนิยมที่มีความแม่นยำเดียวได้อย่างมากเนื่องจากการดำเนินการเดียวกันสามารถดำเนินการกับข้อมูลหลาย ๆ ข้อมูลพร้อมกันได้อีกทั้งยังมีธนาคารการลงทะเบียน * แยกต่างหากสำหรับการดำเนินการ SSE (ฉันสังเกตเห็นในตัวอย่างที่สองของคุณคุณใช้ "float" แทน "double" ทำให้ฉันคิดว่าคุณใช้คณิตศาสตร์แบบ single-precision)

* หมายเหตุ: การใช้คำสั่ง MMX แบบเก่าจะทำให้โปรแกรมช้าลงเนื่องจากคำสั่งเก่าเหล่านั้นใช้การลงทะเบียนเดียวกันกับ FPU ทำให้ไม่สามารถใช้ทั้ง FPU และ MMX ในเวลาเดียวกันได้


8
คณิตศาสตร์ FP ของโปรเซสเซอร์บางตัวอาจเร็วกว่าคณิตศาสตร์จำนวนเต็ม โปรเซสเซอร์ Alpha มีคำสั่งการหาร FP แต่ไม่ใช่จำนวนเต็มดังนั้นการหารจำนวนเต็มจึงต้องทำในซอฟต์แวร์
Gabe

SSEx จะเร่งความเร็วเลขคณิตที่มีความแม่นยำสองเท่าหรือไม่ ฉันขอโทษฉันไม่ค่อยคุ้นเคยกับ SSE
Johannes Schaub - litb

1
@ JohannesSchaub-litb: SSE2 (พื้นฐานสำหรับ x86-64) ได้บรรจุdouble-precision FP ด้วย 64 บิตสองตัวdoubleต่อการลงทะเบียนหนึ่งครั้งการเร่งความเร็วที่เป็นไปได้จะน้อยกว่าfloatรหัสที่เวกเตอร์ได้ดี สเกลาร์floatและdoubleใช้การลงทะเบียน XMM บน x86-64 โดยใช้ x87 แบบเดิมสำหรับlong double. (ดังนั้น @ Dan: ไม่การลงทะเบียน MMX จะไม่ขัดแย้งกับการลงทะเบียน FPU ปกติเนื่องจาก FPU ปกติบน x86-64 เป็นหน่วย SSE MMX จะไม่มีจุดหมายเพราะถ้าคุณสามารถทำ SIMD จำนวนเต็มได้คุณต้องการ 16 ไบต์xmm0..15แทนที่จะเป็น 8 -byte mm0..7และซีพียูสมัยใหม่มี MMX ที่แย่กว่าปริมาณงาน SSE)
Peter Cordes

1
แต่คำสั่งจำนวนเต็ม MMX และ SSE * / AVX2 จะแข่งขันกันเพื่อใช้หน่วยประมวลผลเดียวกันดังนั้นการใช้ทั้งสองอย่างพร้อมกันจึงแทบไม่มีประโยชน์ เพียงใช้ XMM / YMM เวอร์ชันที่กว้างขึ้นเพื่อทำงานให้เสร็จมากขึ้น การใช้จำนวนเต็ม SIMD และ FP ในเวลาเดียวกันจะแข่งขันกับการลงทะเบียนเดียวกัน แต่ x86-64 มี 16 รายการ แต่ขีด จำกัด ปริมาณงานทั้งหมดหมายความว่าคุณไม่สามารถทำงานได้มากเป็นสองเท่าโดยใช้หน่วยการดำเนินการจำนวนเต็มและ FP ควบคู่กัน
Peter Cordes

49

ตัวอย่างเช่น (ตัวเลขที่น้อยกว่าจะเร็วกว่า)

64 บิต Intel Xeon X5550 @ 2.67GHz, gcc 4.1.2 -O3

short add/sub: 1.005460 [0]
short mul/div: 3.926543 [0]
long add/sub: 0.000000 [0]
long mul/div: 7.378581 [0]
long long add/sub: 0.000000 [0]
long long mul/div: 7.378593 [0]
float add/sub: 0.993583 [0]
float mul/div: 1.821565 [0]
double add/sub: 0.993884 [0]
double mul/div: 1.988664 [0]

โปรเซสเซอร์ Dual Core AMD Opteron (tm) 32 บิต 265 @ 1.81GHz, gcc 3.4.6 -O3

short add/sub: 0.553863 [0]
short mul/div: 12.509163 [0]
long add/sub: 0.556912 [0]
long mul/div: 12.748019 [0]
long long add/sub: 5.298999 [0]
long long mul/div: 20.461186 [0]
float add/sub: 2.688253 [0]
float mul/div: 4.683886 [0]
double add/sub: 2.700834 [0]
double mul/div: 4.646755 [0]

ในฐานะที่เป็นแดนชี้ให้เห็นแม้แต่ครั้งเดียวคุณปกติสำหรับความถี่สัญญาณนาฬิกา (ซึ่งสามารถทำให้เข้าใจผิดในตัวเองในการออกแบบไปป์ไลน์) ผลที่ได้จะแตกต่างกันขึ้นอย่างดุเดือดบนซีพียูสถาปัตยกรรม (บุคคลALU / FPUประสิทธิภาพ , เช่นเดียวกับที่เกิดขึ้นจริงจำนวน ALUs / FPUsต่อใช้ได้ หลักในการออกแบบsuperscalarซึ่งมีผลต่อจำนวนการดำเนินการอิสระที่สามารถดำเนินการควบคู่กันได้ - ปัจจัยหลังไม่ได้ใช้โค้ดด้านล่างเนื่องจากการดำเนินการทั้งหมดด้านล่างขึ้นอยู่กับลำดับ)

เกณฑ์มาตรฐานการดำเนินงาน FPU / ALU ของชายผู้น่าสงสาร:

#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
  Type v  = 0;
  // Do not use constants or repeating values
  //  to avoid loop unroll optimizations.
  // All values >0 to avoid division by 0
  // Perform ten ops/iteration to reduce
  //  impact of ++i below on measurements
  Type v0 = (Type)(rand() % 256)/16 + 1;
  Type v1 = (Type)(rand() % 256)/16 + 1;
  Type v2 = (Type)(rand() % 256)/16 + 1;
  Type v3 = (Type)(rand() % 256)/16 + 1;
  Type v4 = (Type)(rand() % 256)/16 + 1;
  Type v5 = (Type)(rand() % 256)/16 + 1;
  Type v6 = (Type)(rand() % 256)/16 + 1;
  Type v7 = (Type)(rand() % 256)/16 + 1;
  Type v8 = (Type)(rand() % 256)/16 + 1;
  Type v9 = (Type)(rand() % 256)/16 + 1;

  double t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v += v0;
    v -= v1;
    v += v2;
    v -= v3;
    v += v4;
    v -= v5;
    v += v6;
    v -= v7;
    v += v8;
    v -= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v /= v0;
    v *= v1;
    v /= v2;
    v *= v3;
    v /= v4;
    v *= v5;
    v /= v6;
    v *= v7;
    v /= v8;
    v *= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
}

int main() {
  my_test< short >("short");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}

8
ทำไมคุณถึงผสม mult และ div มันไม่น่าสนใจถ้า mult อาจจะเร็วกว่ามาก (หรือคาดว่า?) แล้ว div?
Kyss Tao

13
การคูณเร็วกว่าการหารทั้งจำนวนเต็มและทศนิยม ประสิทธิภาพการแบ่งขึ้นอยู่กับขนาดของตัวเลขด้วย ฉันมักจะคิดว่าการหารช้าลง ~ 15 เท่า
Sogartar

4
pastebin.com/Kx8WGUfgฉันใช้เกณฑ์มาตรฐานของคุณและแยกการดำเนินการแต่ละอย่างออกเป็นลูปของตัวเองและเพิ่มvolatileเพื่อให้แน่ใจ ใน Win64 FPU ไม่ได้ใช้งานและ MSVC จะไม่สร้างโค้ดสำหรับมันดังนั้นจึงรวบรวมคำสั่งโดยใช้mulssและdivssXMM ที่นั่นซึ่งเร็วกว่า FPU ใน Win32 ถึง 25 เท่า เครื่องทดสอบคือ Core i5 M 520 @ 2.40GHz
James Dunne

4
@JamesDunne โปรดระวังสำหรับ fp ops vจะไปถึง 0 หรือ +/- inf อย่างรวดเร็วอย่างรวดเร็วซึ่งอาจจะถือว่าหรือไม่ก็ได้ (ในทางทฤษฎี) เป็นกรณีพิเศษ / fastpatheed โดยการใช้งาน fpu บางอย่าง
vladr

3
"เกณฑ์มาตรฐาน" นี้ไม่มีความเท่าเทียมกันของข้อมูลสำหรับการดำเนินการนอกคำสั่งเนื่องจากการดำเนินการทุกอย่างทำด้วยตัวสะสมเดียวกัน ( v) ในการออกแบบล่าสุดของ Intel การแบ่งไม่ได้เป็นไพพ์เลย ( divss/ divpsมีเวลาแฝง 10-14 รอบและปริมาณงานที่ตรงกัน) mulssอย่างไรก็ตามเป็นเวลาแฝง 5 รอบ แต่สามารถออกได้ทุกรอบ (หรือสองต่อรอบใน Haswell เนื่องจากพอร์ต 0 และพอร์ต 1 ทั้งคู่มีตัวคูณสำหรับ FMA)
Peter Cordes

23

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

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

คุณมีแนวโน้มที่จะพอดีกับจำนวนเต็มจำนวนมากขึ้นในแคชในแต่ละครั้งเช่นกัน ดังนั้นเวอร์ชันจุดคงที่อาจทำงานได้ดีกว่ารุ่นโฟลตตามลำดับขนาดแม้ในเครื่องที่ FPU มีทรูพุตที่สูงกว่าในทางทฤษฎี


4
+1 เพื่อชี้ให้เห็นว่าการวัดผลที่ไร้เดียงสาสามารถให้ลูป 0 เวลาได้อย่างไรเนื่องจากการดำเนินการจำนวนเต็มคงที่ที่ยังไม่ได้ควบคุม ยิ่งไปกว่านั้นคอมไพลเลอร์สามารถละทิ้งลูป (จำนวนเต็มหรือ FP) ได้อย่างสมบูรณ์หากไม่ได้ใช้ผลลัพธ์จริง
vladr

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

@GameAlchemist: คอมไพเลอร์จำนวนมากกำจัดการเรียกไปยังฟังก์ชันที่ว่างเปล่าซึ่งเป็นผลข้างเคียงของการอินไลน์ คุณต้องพยายามอย่างเต็มที่เพื่อป้องกันสิ่งนั้น
Ben Voigt

OP ฟังดูเหมือนเขากำลังพูดถึงการใช้จำนวนเต็มสำหรับสิ่งที่ FP จะพอดีกับธรรมชาติมากกว่าดังนั้นจึงต้องใช้รหัสจำนวนเต็มมากกว่าเพื่อให้ได้ผลลัพธ์เช่นเดียวกับรหัส FP ในกรณีนี้ให้ใช้ FP ตัวอย่างเช่นบนฮาร์ดแวร์ที่มี FPU (เช่นเดสก์ท็อป CPU) ตัวถอดรหัส MP3 จำนวนเต็มจุดคงที่จะทำงานช้ากว่า (และมีข้อผิดพลาดในการปัดเศษมากกว่าเล็กน้อย) มากกว่าตัวถอดรหัสทศนิยม การใช้งานจุดคงที่ของตัวแปลงสัญญาณส่วนใหญ่มีอยู่เพื่อทำงานบนซีพียู ARM ที่ถอดออกโดยไม่มีฮาร์ดแวร์ FP มีเพียง FP ที่จำลองช้าเท่านั้น
Peter Cordes

ตัวอย่างหนึ่งสำหรับจุดแรก: บน x86-64 กับ AVX-512 มีการลงทะเบียน GP เพียง 16 เครื่อง แต่การลงทะเบียน 32 zmm ดังนั้นการคำนวณทศนิยมแบบสเกลาร์อาจเร็วกว่า
phuclv

18

การเพิ่มนั้นเร็วกว่าrandมากดังนั้นโปรแกรมของคุณจึงไร้ประโยชน์ (โดยเฉพาะ)

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

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


ใช่เช่นเดียวกับการแปลงจากจำนวนเต็มแรนด์เป็นจำนวนทศนิยมในเวอร์ชันทศนิยม มีแนวคิดเกี่ยวกับวิธีที่ดีกว่าในการทดสอบสิ่งนี้หรือไม่?
maxpenguin

1
หากคุณกำลังพยายามสร้างโปรไฟล์ให้ดูที่ POSIX timespec_tหรืออะไรที่คล้ายกัน บันทึกเวลาที่จุดเริ่มต้นและจุดสิ้นสุดของลูปและรับผลต่าง จากนั้นย้ายการrandสร้างข้อมูลออกจากลูป ตรวจสอบให้แน่ใจว่าอัลกอริทึมของคุณได้รับข้อมูลทั้งหมดจากอาร์เรย์และทำให้ข้อมูลทั้งหมดอยู่ในอาร์เรย์ นั่นทำให้อัลกอริทึมจริงของคุณด้วยตัวเองและรับการตั้งค่า malloc การพิมพ์ผลลัพธ์ทุกอย่างยกเว้นการสลับงานและขัดจังหวะออกจากลูปการทำโปรไฟล์ของคุณ
Mike D.

3
@maxpenguin: คำถามคือสิ่งที่คุณกำลังทดสอบ อาร์เทมสันนิษฐานว่าคุณกำลังทำกราฟิกคาร์ลพิจารณาว่าคุณอยู่บนแพลตฟอร์มฝังตัวหรือไม่และมี FP หรือไม่ฉันคิดว่าคุณกำลังเข้ารหัสวิทยาศาสตร์สำหรับเซิร์ฟเวอร์ คุณไม่สามารถสรุปหรือ "เขียน" เกณฑ์มาตรฐานได้ เกณฑ์มาตรฐานจะถูกสุ่มตัวอย่างจากงานจริงที่โปรแกรมของคุณทำ สิ่งหนึ่งที่ฉันบอกคุณได้ก็คือมันจะไม่คง "ความเร็วเท่าเดิม" หากคุณสัมผัสองค์ประกอบที่สำคัญต่อประสิทธิภาพในโปรแกรมของคุณไม่ว่าจะเป็นอะไรก็ตาม
Potatoswatter

จุดดีและคำตอบที่ดี ขณะนี้เรามีฐานรหัสที่ครอบคลุม จริงๆแล้วฉันได้คิดสรุปว่าเรา "ต้องไม่ใช้ float เนื่องจากการคำนวณจำนวนเต็มเร็วกว่า" - และฉันกำลังมองหาวิธี (ถ้าเป็นจริง) เพื่อหักล้างข้อสันนิษฐานทั่วไปนี้ ฉันตระหนักดีว่ามันคงเป็นไปไม่ได้ที่จะทำนายผลลัพธ์ที่แน่นอนสำหรับเราโดยไม่ต้องทำงานทั้งหมดและจัดทำโปรไฟล์ในภายหลัง อย่างไรก็ตามขอบคุณสำหรับความช่วยเหลือของคุณ
maxpenguin

18

TIL สิ่งนี้แตกต่างกันไป (มาก) นี่คือผลลัพธ์บางส่วนโดยใช้คอมไพเลอร์ gnu (btw ฉันยังตรวจสอบโดยการรวบรวมบนเครื่อง gnu g ++ 5.4 จาก xenial นั้นเร็วกว่า 4.6.3 จาก linaro อย่างแม่นยำ)

Intel i7 4700MQ xenial

short add: 0.822491
short sub: 0.832757
short mul: 1.007533
short div: 3.459642
long add: 0.824088
long sub: 0.867495
long mul: 1.017164
long div: 5.662498
long long add: 0.873705
long long sub: 0.873177
long long mul: 1.019648
long long div: 5.657374
float add: 1.137084
float sub: 1.140690
float mul: 1.410767
float div: 2.093982
double add: 1.139156
double sub: 1.146221
double mul: 1.405541
double div: 2.093173

Intel i3 2370M มีผลลัพธ์ที่คล้ายกัน

short add: 1.369983
short sub: 1.235122
short mul: 1.345993
short div: 4.198790
long add: 1.224552
long sub: 1.223314
long mul: 1.346309
long div: 7.275912
long long add: 1.235526
long long sub: 1.223865
long long mul: 1.346409
long long div: 7.271491
float add: 1.507352
float sub: 1.506573
float mul: 2.006751
float div: 2.762262
double add: 1.507561
double sub: 1.506817
double mul: 1.843164
double div: 2.877484

Intel (R) Celeron (R) 2955U (Acer C720 Chromebook ที่ใช้ xenial)

short add: 1.999639
short sub: 1.919501
short mul: 2.292759
short div: 7.801453
long add: 1.987842
long sub: 1.933746
long mul: 2.292715
long div: 12.797286
long long add: 1.920429
long long sub: 1.987339
long long mul: 2.292952
long long div: 12.795385
float add: 2.580141
float sub: 2.579344
float mul: 3.152459
float div: 4.716983
double add: 2.579279
double sub: 2.579290
double mul: 3.152649
double div: 4.691226

DigitalOcean 1GB Droplet Intel (R) Xeon (R) CPU E5-2630L v2 (ทำงานที่เชื่อถือได้)

short add: 1.094323
short sub: 1.095886
short mul: 1.356369
short div: 4.256722
long add: 1.111328
long sub: 1.079420
long mul: 1.356105
long div: 7.422517
long long add: 1.057854
long long sub: 1.099414
long long mul: 1.368913
long long div: 7.424180
float add: 1.516550
float sub: 1.544005
float mul: 1.879592
float div: 2.798318
double add: 1.534624
double sub: 1.533405
double mul: 1.866442
double div: 2.777649

โปรเซสเซอร์ AMD Opteron (tm) 4122 (แม่นยำ)

short add: 3.396932
short sub: 3.530665
short mul: 3.524118
short div: 15.226630
long add: 3.522978
long sub: 3.439746
long mul: 5.051004
long div: 15.125845
long long add: 4.008773
long long sub: 4.138124
long long mul: 5.090263
long long div: 14.769520
float add: 6.357209
float sub: 6.393084
float mul: 6.303037
float div: 17.541792
double add: 6.415921
double sub: 6.342832
double mul: 6.321899
double div: 15.362536

ใช้รหัสจากhttp://pastebin.com/Kx8WGUfgเป็นbenchmark-pc.c

g++ -fpermissive -O3 -o benchmark-pc benchmark-pc.c

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

ข้อยกเว้นที่น่าสังเกตอย่างหนึ่งคือ ALU mul vs FPU mul การบวกและการลบดูเหมือนจะแตกต่างกันเล็กน้อย

นี่คือด้านบนในรูปแบบแผนภูมิ (คลิกเพื่อดูขนาดเต็มด้านล่างจะเร็วกว่าและดีกว่า):

แผนภูมิข้อมูลข้างต้น

อัปเดตเพื่อรองรับ @Peter Cordes

https://gist.github.com/Lewiscowles1986/90191c59c9aedf3d08bf0b129065cccc

i7 4700MQ Linux Ubuntu Xenial 64 บิต (ใช้แพตช์ทั้งหมดถึง 2018-03-13)
    short add: 0.773049
    short sub: 0.789793
    short mul: 0.960152
    short div: 3.273668
      int add: 0.837695
      int sub: 0.804066
      int mul: 0.960840
      int div: 3.281113
     long add: 0.829946
     long sub: 0.829168
     long mul: 0.960717
     long div: 5.363420
long long add: 0.828654
long long sub: 0.805897
long long mul: 0.964164
long long div: 5.359342
    float add: 1.081649
    float sub: 1.080351
    float mul: 1.323401
    float div: 1.984582
   double add: 1.081079
   double sub: 1.082572
   double mul: 1.323857
   double div: 1.968488
โปรเซสเซอร์ AMD Opteron (tm) 4122 (แม่นยำ, โฮสติ้งที่ใช้ร่วมกัน DreamHost)
    short add: 1.235603
    short sub: 1.235017
    short mul: 1.280661
    short div: 5.535520
      int add: 1.233110
      int sub: 1.232561
      int mul: 1.280593
      int div: 5.350998
     long add: 1.281022
     long sub: 1.251045
     long mul: 1.834241
     long div: 5.350325
long long add: 1.279738
long long sub: 1.249189
long long mul: 1.841852
long long div: 5.351960
    float add: 2.307852
    float sub: 2.305122
    float mul: 2.298346
    float div: 4.833562
   double add: 2.305454
   double sub: 2.307195
   double mul: 2.302797
   double div: 5.485736
Intel Xeon E5-2630L v2 @ 2.4GHz (Trusty 64-bit, DigitalOcean VPS)
    short add: 1.040745
    short sub: 0.998255
    short mul: 1.240751
    short div: 3.900671
      int add: 1.054430
      int sub: 1.000328
      int mul: 1.250496
      int div: 3.904415
     long add: 0.995786
     long sub: 1.021743
     long mul: 1.335557
     long div: 7.693886
long long add: 1.139643
long long sub: 1.103039
long long mul: 1.409939
long long div: 7.652080
    float add: 1.572640
    float sub: 1.532714
    float mul: 1.864489
    float div: 2.825330
   double add: 1.535827
   double sub: 1.535055
   double mul: 1.881584
   double div: 2.777245

gcc5 อาจทำเวกเตอร์อัตโนมัติบางอย่างที่ gcc4.6 ไม่ได้? กำลังbenchmark-pcวัดปริมาณงานและเวลาในการตอบสนองร่วมกันหรือไม่ ใน Haswell ของคุณ (i7 4700MQ) การคูณจำนวนเต็มคือ 1 ต่อปริมาณงานของนาฬิกา, เวลาในการตอบสนอง 3 รอบ แต่การเพิ่ม / ย่อยจำนวนเต็มคือ 4 ต่อปริมาณงานของนาฬิกา, เวลาแฝง 1 รอบ ( agner.org/optimize ) ดังนั้นน่าจะมีค่าใช้จ่ายในการวนซ้ำจำนวนมากที่เจือจางตัวเลขเหล่านั้นเพื่อเพิ่มและ mul ให้ออกมาใกล้เคียงกันมาก (เพิ่มแบบยาว: 0.824088 เทียบกับ long mul: 1.017164) (ค่าเริ่มต้น gcc คือไม่คลายการวนซ้ำยกเว้นการคลายการนับการวนซ้ำที่ต่ำมาก)
Peter Cordes

และ BTW ทำไมไม่ทดสอบintเท่านั้นshortและlong? บน Linux x86-64 shortเป็น 16 บิต (และมีการชะลอตัวลงทะเบียนบางส่วนในบางกรณี) ในขณะที่longและlong longเป็นประเภท 64 บิต (อาจจะออกแบบมาสำหรับ Windows ที่ x86-64 ยังใช้ 32 บิตlongหรืออาจจะออกแบบมาสำหรับโหมด 32 บิต) บน Linux x32 ABI มี 32 บิตlongในโหมด 64 บิตดังนั้นหากคุณติดตั้งไลบรารีไว้ ใช้gcc -mx32คอมไพเลอร์สำหรับ ILP32 หรือเพียงแค่ใช้-m32และดูที่longตัวเลข
Peter Cordes

และคุณควรตรวจสอบจริงๆว่าคอมไพเลอร์ของคุณเป็นเวกเตอร์อัตโนมัติหรือไม่ เช่นใช้addpsในการลงทะเบียน XMM แทนการaddssที่จะทำ 4 FP addssเพิ่มในแบบคู่ขนานในการเรียนการสอนที่เร็วที่สุดเท่าที่เกลา (ใช้-march=nativeเพื่ออนุญาตให้ใช้ชุดคำสั่งใด ๆ ก็ตามที่ CPU ของคุณรองรับไม่ใช่เฉพาะ SSE2 baseline สำหรับ x86-64)
Peter Cordes

@cincodenada โปรดทิ้งชาร์ตที่แสดงเต็ม 15 ด้านไว้เพราะมันเป็นตัวอย่างของการแสดง
MrMesees

@PeterCordes ฉันจะพยายามดูพรุ่งนี้ขอบคุณสำหรับความพากเพียรของคุณ
MrMesees

7

สองประเด็นที่ต้องพิจารณา -

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

เช่นเคยวิธีเดียวที่จะมั่นใจได้คือการกำหนดโปรไฟล์โปรแกรมจริงของคุณ

ประเด็นที่สองคือซีพียูส่วนใหญ่ในปัจจุบันมีคำแนะนำ SIMD สำหรับจุดลอยตัวที่สามารถทำงานกับค่าทศนิยมหลายค่าพร้อมกันได้ ตัวอย่างเช่นคุณสามารถโหลด 4 โฟลลงในทะเบียน SSE เดียวและทำการคูณ 4 ตัวทั้งหมดแบบขนาน หากคุณสามารถเขียนบางส่วนของโค้ดของคุณใหม่เพื่อใช้คำแนะนำ SSE ได้ดูเหมือนว่าจะเร็วกว่าเวอร์ชันจำนวนเต็ม Visual c ++ มีฟังก์ชันภายในคอมไพเลอร์สำหรับข้อมูลบางอย่างที่http://msdn.microsoft.com/en-us/library/x5c07e2a(v=VS.80).aspx


โปรดทราบว่าใน Win64 คำแนะนำ FPU ไม่ได้สร้างขึ้นโดยคอมไพเลอร์ MSVC อีก จุดลอยตัวใช้คำแนะนำ SIMD ที่นั่นเสมอ สิ่งนี้ทำให้ความคลาดเคลื่อนของความเร็วระหว่าง Win32 และ Win64 เกี่ยวกับความล้มเหลวเป็นอย่างมาก
James Dunne

5

เวอร์ชันทศนิยมจะช้าลงมากหากไม่มีการดำเนินการที่เหลือ เนื่องจากการเพิ่มทั้งหมดเป็นแบบลำดับซีพียูจะไม่สามารถขนานผลรวมได้ เวลาในการตอบสนองจะวิกฤต โดยทั่วไปเวลาในการตอบสนองของการเพิ่ม FPU คือ 3 รอบในขณะที่การเพิ่มจำนวนเต็มเท่ากับ 1 รอบ อย่างไรก็ตามตัวแบ่งสำหรับตัวดำเนินการส่วนที่เหลือน่าจะเป็นส่วนที่สำคัญเนื่องจากไม่ได้เชื่อมต่อกับซีพียูสมัยใหม่อย่างเต็มที่ ดังนั้นสมมติว่าคำสั่งหาร / ส่วนที่เหลือจะใช้เวลาส่วนใหญ่ความแตกต่างเนื่องจากการเพิ่มเวลาแฝงจะมีค่าน้อย


4

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

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

แก้ไข:

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

http://en.wikipedia.org/wiki/Bresenham's_algorithm


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

4

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

  • ทันทีที่คุณต้องการการดำเนินการจำนวนเต็มมากขึ้นคุณอาจต้องการมากกว่านี้ดังนั้นข้อได้เปรียบด้านความเร็วเล็กน้อยนั้นมากกว่าการดำเนินการเพิ่มเติม

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


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

3

ฉันทำการทดสอบที่เพิ่งเพิ่ม 1 เข้าไปในตัวเลขแทน Rand () ผลลัพธ์ (บน x86-64) คือ:

  • สั้น: 4.260 วินาที
  • int: 4.020 วินาที
  • ยาว: 3.350 วินาที
  • ลอย: 7.330 วินาที
  • สองเท่า: 7.210 วินาที

1
แหล่งรวบรวมตัวเลือกและวิธีการจับเวลา? ฉันรู้สึกประหลาดใจเล็กน้อยกับผลลัพธ์
GManNickG

ลูปเดียวกับ OP ที่มี "rand ()% 365" แทนที่ด้วย "1" ไม่มีการเพิ่มประสิทธิภาพ เวลาของผู้ใช้จากคำสั่ง "เวลา"
dan04

13
"ไม่มีการเพิ่มประสิทธิภาพ" เป็นกุญแจสำคัญ คุณไม่เคยปิดโปรไฟล์ด้วยการปิดการเพิ่มประสิทธิภาพโปรไฟล์ในโหมด "ปล่อย" เสมอ
Dean Harding

2
ในกรณีนี้การปิดการเพิ่มประสิทธิภาพจะบังคับให้ op เกิดขึ้นและทำโดยเจตนา - มีการวนซ้ำเพื่อขยายเวลาให้มีขนาดการวัดที่เหมาะสม การใช้ค่าคงที่ 1 จะลบต้นทุนของ rand () คอมไพเลอร์การเพิ่มประสิทธิภาพที่ชาญฉลาดเพียงพอจะเห็น 1 เพิ่ม 100,000,000 ครั้งโดยไม่มีทางออกจากลูปและเพิ่ม 100000000 ในครั้งเดียว การจัดเรียงแบบนั้นได้รับการตอบสนองวัตถุประสงค์ทั้งหมดใช่หรือไม่
Stan Rogers

7
@ สแตนทำให้ตัวแปรผันผวน แม้แต่คอมไพเลอร์ที่ปรับให้เหมาะสมอย่างชาญฉลาดก็ควรให้เกียรติแก่หน่วยงานหลาย ๆ
vladr

0

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


1
โปรดพิจารณาดูสิ่งนี้อีกครั้งที่นำเสนอมากกว่าความคิดเห็น (โดยเฉพาะอย่างยิ่งเนื่องจากความคิดเห็นดูเหมือนจะบินไปเผชิญหน้ากับข้อเท็จจริงที่รวบรวม)
MrMesees

1
@MrMesees แม้ว่าคำตอบนี้จะไม่มีประโยชน์มากนัก แต่ฉันก็บอกว่ามันสอดคล้องกับการทดสอบที่คุณทำ และเรื่องไม่สำคัญทางประวัติศาสตร์ก็น่าจะดีเช่นกัน
Jonatan Öström

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