อาเรย์ขนาดใหญ่ขนาด 1 เมกะไบต์ทำให้ CPU สูง?

ฉันมีแอปพลิเคชันเซิร์ฟเวอร์แบบมัลติเธรด แอปพลิเคชันนี้รับข้อมูลจากซ็อกเก็ตแล้วจัดการข้อมูลเหล่านี้ เช่น การแกะแพ็กเกจ การเพิ่มคิวข้อมูล เป็นต้น โดยมีฟังก์ชันดังนี้ ฟังก์ชันนี้ถูกเรียกบ่อยครั้ง มีคำสั่ง select และหากพบว่ามีข้อมูลก็จะเรียกใช้ฟังก์ชันนี้เพื่อรับ):

         //the main function used to receive 
         //file data from clients
         void service(void){
              while(1){
                   ....
                   struct timeval timeout;
                   timeout.tv_sec = 3;

                   ...
                   ret = select(maxFd+1, &read_set, NULL, NULL, &timeout);
                   if (ret > 0){
                       //get socket from SocketsMap
                       //if fd in SocketsMap and its being set
                       //then receive data from the socket
                       receive_data(fd);
                   }
              }
         } 

         void receive_data(int fd){
              const int ONE_MEGA = 1024 * 1024;

              //char *buffer = new char[ONE_MEGA]; consumes much less CPU
              char buffer[ONE_MEGA]; // cause high CPU 
              int readn = recv(fd, buffer, ONE_MEGA, 0);

              //handle the data
         }

ฉันพบว่าด้านบนใช้ CPU มากเกินไป - ปกติ 80% ถึง 90% แต่ถ้าฉันสร้างบัฟเฟอร์จากฮีปแทน CPU จะเหลือเพียง 14% ทำไม

[อัปเดต]
เพิ่มโค้ดเพิ่มเติม

[update2]
สิ่งที่สำคัญที่สุดคือฉันยังได้เขียนเซิร์ฟเวอร์และไคลเอนต์รับข้อมูลแบบธรรมดาอีกเครื่องหนึ่งด้วย เซิร์ฟเวอร์เพียงรับข้อมูลจากซ็อกเก็ตแล้วทิ้งไป การจัดสรรพื้นที่ทั้งสองประเภททำงานเกือบจะเหมือนกัน ไม่มีความแตกต่างในการใช้งาน CPU มากนัก ในแอปพลิเคชันเซิร์ฟเวอร์แบบมัลติเธรดที่มีปัญหา ฉันยังรีเซ็ตขนาดสแต็กกระบวนการเป็น 30M โดยใช้อาร์เรย์ยังคงส่งผลให้เกิดปัญหา แต่การจัดสรรจากฮีปจะช่วยแก้ปัญหาได้ ฉันไม่รู้ว่าทำไม

เกี่ยวกับ "sizeof(buffer)" ขอขอบคุณที่ชี้ให้เห็นสิ่งนี้ แต่ฉันมั่นใจ 100% ว่าไม่ใช่ปัญหา เพราะในแอปพลิเคชันของฉัน ฉันไม่ได้ใช้ sizeof(buffer) แต่เป็น ONE_MEGA (1024*1024) แทน .

ยังมีอีกสิ่งหนึ่งที่ต้องพูดถึงแม้ว่าฉันจะไม่แน่ใจว่ามีประโยชน์หรือไม่ก็ตาม การแทนที่อาร์เรย์ด้วยอาร์เรย์ที่เล็กกว่า เช่น "char buffer[1024] ยังช่วยลดการใช้ cpu อย่างมากอีกด้วย

[update3]
ซ็อกเก็ตทั้งหมดอยู่ในโหมดไม่ปิดกั้น


person Wallace    schedule 29.07.2013    source แหล่งที่มา
comment
ไม่สมเหตุสมผลเลยสำหรับฉัน   -  person Mats Petersson    schedule 29.07.2013
comment
สวัสดีปีเตอร์สสัน ขออภัย ฉันไม่ค่อยเข้าใจว่าทำไมคุณถึงพูดเช่นนั้น นี่อาจเป็นข้อเท็จจริงที่ว่าการจัดสรรขนาดสแต็กขนาดใหญ่ของอาเรย์มักทำให้ CPU สูง แต่คุณช่วยอธิบายได้ไหม   -  person Wallace    schedule 29.07.2013
comment
กระบวนการของคุณทำอะไรเมื่อคุณ strace'd มัน? /bin/time แสดงจำนวน pagefault ที่แตกต่างกันอย่างสม่ำเสมอสำหรับทั้งสองเวอร์ชันหรือไม่   -  person Useless    schedule 29.07.2013
comment
จากประสบการณ์ของฉันการใช้สแต็กจำนวนมากไม่ควรทำให้บางสิ่งใช้ CPU มากขึ้น แต่ขอผมทำการทดลองบ้างนะครับ...   -  person Mats Petersson    schedule 29.07.2013
comment
มันกำลังรับข้อมูล ฉันใช้ top เพื่อดูการใช้งาน CPU และพบว่าโดยปกติจะใช้ cpu 80% แต่ถ้าฉันใช้ buffer = new char[one_mega] แทน กระบวนการเดียวกันที่ทำการดำเนินการเดียวกันจะใช้เพียง 14%   -  person Wallace    schedule 29.07.2013
comment
เช่นเดียวกับปัญหาด้านประสิทธิภาพใดๆ คุณได้คอมไพล์โดยเปิดการปรับให้เหมาะสมก่อนที่จะเปรียบเทียบทั้งสองเวอร์ชันหรือไม่   -  person syam    schedule 29.07.2013
comment
คุณยังปล่อยบัฟเฟอร์ของคุณหรือไม่? มิฉะนั้น อาจเป็นไปได้ว่า 14% ของคุณสะท้อนถึงการแลกเปลี่ยน...   -  person Mats Petersson    schedule 29.07.2013
comment
คุณเปลี่ยนบรรทัด recv ของคุณสำหรับการทดสอบการจัดสรรแบบไดนามิกหรือไม่ หากคุณไม่ทำ sizeof(buffer) ส่งผ่านไปยัง recv จะบอกว่าคุณต้องการอ่าน sizeof(char*) (อาจเป็น 4 หรือ 8 ไบต์) แทนที่จะเป็น 1Mb   -  person simonc    schedule 29.07.2013
comment
@syam ทั้งคู่ใช้ระดับการปรับให้เหมาะสม O3   -  person Wallace    schedule 29.07.2013
comment
แน่นอนว่าการจัดสรรบนสแต็กนั้นเร็วกว่าการจัดสรรบนฮีปมาก แต่ทำไมคุณถึงจัดสรรทุกครั้งที่ได้รับ ทำไมไม่จัดสรรบัฟเฟอร์หนึ่งบัฟเฟอร์และเก็บไว้ตลอดไป   -  person PlasmaHH    schedule 29.07.2013
comment
@PlasmaHH: ฉันไม่เชื่อว่าการจัดสรรครั้งเดียวหรือหลายครั้งจะสร้างความแตกต่างได้มาก - เมื่อคุณรวบรวมฮีปแล้วมันอาจจะทำให้คุณมีหน่วยความจำก้อนเดียวกันในแต่ละครั้ง [สมมติว่ามีไม่มากเกินไป แน่นอนว่าการจัดสรรอื่น ๆ เกิดขึ้น] และควรให้การใช้งาน cpu สูงขึ้นหากการจัดสรรฮีปช้า   -  person Mats Petersson    schedule 29.07.2013
comment
@PlasmaHH ขอบคุณสำหรับคำแนะนำ ฉันควรใช้มันด้วยวิธีนี้ ฉันจะ!   -  person Wallace    schedule 29.07.2013
comment
ฉันเดาว่า select() จะกลับมาแม้ว่าจะยังไม่มีข้อมูลใด ๆ ให้อ่าน จากนั้นการเรียก recv() ที่ไม่บล็อกของคุณจะส่งกลับ EWOULDBLOCK จากนั้นคุณกลับไปเลือก () อีกครั้ง ซึ่งจะตื่นขึ้นมาทันที อีกครั้งและต่อๆ ไป ดังนั้นจึงยุ่งวุ่นวายและกิน CPU อาจเป็นการดีที่จะตรวจสอบอีกครั้งว่าคุณกำลังเรียก FD_ZERO และ FD_SET อย่างถูกต้อง และทดสอบ FD_ISSET ก่อนที่จะเรียก gets_data() และยังพิมพ์ค่าที่ส่งคืนโดย recv() เพื่อดูว่าให้ข้อมูลกับคุณจริงหรือไม่ เรียกว่าหรือไม่   -  person Jeremy Friesner    schedule 31.07.2013


คำตอบ (3)


ฉันเพิ่งเขียนสิ่งนี้:

#include <iostream>
#include <cstdio>

using namespace std;

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

const int M = 1024*1024;

void bigstack()
{
    FILE *f = fopen("test.txt", "r");
    unsigned long long time;
    char buffer[M];

    time = rdtsc();
    fread(buffer, M, 1, f);
    time = rdtsc() - time;
    fclose(f);
    cout << "bs: Time = " << time / 1000 << endl;
}


void bigheap()
{
    FILE *f = fopen("test.txt", "r");
    unsigned long long time;
    char *buffer = new char[M];

    time = rdtsc();
    fread(buffer, M, 1, f);
    time = rdtsc() - time;
    delete [] buffer;
    fclose(f);
    cout << "bh: Time = " << time / 1000 << endl;
}



int main()
{
    for(int i = 0; i < 10; i++)
    {
    bigstack();
    bigheap();
    }
}

ผลลัพธ์จะเป็นดังนี้:

bs: Time = 8434
bh: Time = 7242
bs: Time = 1094
bh: Time = 2060
bs: Time = 842
bh: Time = 830
bs: Time = 785
bh: Time = 781
bs: Time = 782
bh: Time = 804
bs: Time = 782
bh: Time = 778
bs: Time = 792
bh: Time = 809
bs: Time = 785
bh: Time = 786
bs: Time = 782
bh: Time = 829
bs: Time = 786
bh: Time = 781

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

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

person Mats Petersson    schedule 29.07.2013
comment
ว้าว คุณเขียนแอปทดสอบได้เร็วมาก ยอดเยี่ยมมาก! การรีเซ็ตอาร์เรย์เป็นขนาด 1,024 ไบต์ยังลดการใช้งาน CPU ลงอย่างมาก! ข้อมูลนี้มีประโยชน์หรือไม่? - person Wallace; 29.07.2013
comment
ฉันคิดว่าเวลาที่ยาวที่สุดคือการสร้างไฟล์ 1MB เพื่ออ่านจริง ๆ ... ;) - ฉันไม่สามารถอธิบายได้ว่าทำไมการใช้บัฟเฟอร์ที่เล็กกว่าจึงสร้างความแตกต่างได้มาก คุณช่วยยืนยันได้ไหมว่าคุณใช้ ONE_MEGA เป็นขนาดเมื่อคุณใช้ new เป็นบัฟเฟอร์ - person Mats Petersson; 29.07.2013
comment
ใช่ ฉันแน่ใจ 100% ฉันใช้เวลาช่วงบ่ายทั้งหมดเล่นกับข้อมูลโค้ด ฉันไม่สามารถอธิบายได้ว่าทำไมอาร์เรย์ถึงทำให้แอปใช้ cpu มากขึ้น สิ่งแรกที่ฉันคิดคือขนาดสแต็ก ฉันสงสัยว่าสแต็กจะไม่เพียงพอดังนั้นฉันจึงรีเซ็ตขนาดสแต็กเป็น 30M โดยใช้ setrlimit() แต่ผลลัพธ์ก็เหมือนเดิม - person Wallace; 29.07.2013
comment
ฉันไม่เห็นเหตุผลว่าทำไม recv จึงควรแตกต่างจาก fread มากในการจัดการหน่วยความจำ - อาจใช้เวลานานกว่าหรือสั้นกว่าเล็กน้อยในการคัดลอกข้อมูลที่ได้รับจริงๆ แต่นอกเหนือจากนั้น ฉันไม่เห็นว่าเหตุใด เกณฑ์มาตรฐานไม่ตรงกับสิ่งที่ระบบของคุณกำลังทำอยู่ สมมติว่าระบบของคุณเป็นระบบ x86 คุณสามารถเรียกใช้โค้ดของฉันได้ (คุณจะต้องสร้างไฟล์ test.txt ของคุณเองโดยมีข้อมูลประมาณ 1M ในนั้น) - person Mats Petersson; 29.07.2013
comment
เป็นแอปพลิเคชันเซิร์ฟเวอร์สำหรับการส่งต่อไฟล์จากไคลเอนต์ A ไปยัง ClientB, ClientC,... ประการแรก มันจะอ่านข้อมูลไฟล์จากซ็อกเก็ต clientA จากนั้นเขียนลงในไฟล์ในเครื่องและส่งต่อไปยังไคลเอนต์อื่นในที่สุด ตอนนี้ฉันพบการทำงานของซ็อกเก็ตการอ่าน (อ่านข้อมูลจากซ็อกเก็ตแล้วทิ้งไป) ใช้การใช้งาน CPU มากเกินไป - person Wallace; 29.07.2013
comment
อาจจะมองไม่เห็น แต่ลองตรวจสอบว่าการข้ามเส้นขอบขนาดบัฟเฟอร์ขนาด 4k สร้างความแตกต่างหรือไม่ - person BeginEnd; 29.07.2013
comment
สำหรับบัฟเฟอร์ขนาด 1MB จะมีการข้ามขอบเขตหน้า 255 ถึง 257 และโดยทั่วไปแล้ว บัฟเฟอร์ที่จัดสรรโดย new จะไม่ถูกจัดแนวหน้าเช่นกัน - person Mats Petersson; 29.07.2013
comment
ใช่ ฉันรู้ แต่เขาเขียนว่า การแทนที่อาเรย์ด้วยอันที่เล็กกว่า เช่น char buffer[1024]; ยังลดการใช้งาน cpu ลงอย่างมาก ดังนั้นฉันคิดว่าเขาสามารถหาขนาดได้เมื่อการใช้งาน CPU สูง อาจมีเบาะแสบางอย่างหรือไม่ แต่คุณไม่รู้ว่าเขาจะไม่ตรวจสอบเรื่องนี้หรือไม่ - person BeginEnd; 29.07.2013
comment
ฉันไม่มีความคิด ฉันแค่บอกว่าการข้ามหน้า 255 หรือ 257 หน้าน่าจะสร้างความแตกต่างเพียงเล็กน้อย (ถ้ามี) - person Mats Petersson; 29.07.2013
comment
@BeginEnd พรุ่งนี้ฉันจะไปตรวจสอบเส้นขอบขนาดนี้ได้ - person Wallace; 29.07.2013
comment
Petersson ฉันขอขอบคุณสำหรับความช่วยเหลือของคุณในคำถามนี้ ฉันเห็นว่าคุณแสดงความคิดเห็นในโพสต์อื่นของฉัน ฉันจะยอมรับความคิดเห็นของคุณในโพสต์อื่นของฉันเป็นคำตอบของโพสต์นี้ ถ้าคุณไม่รังเกียจที่จะอัปเดตความคิดเห็นของคุณในโพสต์นี้ ขอบคุณอีกครั้งปีเตอร์สัน - person Wallace; 29.07.2013
comment
เพียงยอมรับคำตอบนี้และโหวตอีกอัน? บันทึกเนื้อหาเดียวกันโดยอยู่ในคำตอบที่แตกต่างกันสองคำตอบ - person Mats Petersson; 29.07.2013
comment
ฉันไม่เห็นเหตุผลที่เชื่อได้ว่า recv ผ่านเครือข่ายและ fread จากไฟล์ในเครื่องจะมีพฤติกรรมที่คล้ายกัน fread มีการบัฟเฟอร์เพิ่มเติมอีกชั้น นอกเหนือจากความเป็นไปได้ในการอ่านบางส่วนด้วย recv บนซ็อกเก็ตสตรีม - person Ben Voigt; 30.07.2013
comment
@BenVoigt: และนั่นจะส่งผลต่อจำนวน CPU ที่ใช้โดยขึ้นอยู่กับว่าการจัดสรรมาจากไหน แน่นอนว่าการโทรนั้นมีพฤติกรรมแตกต่างออกไปในบางด้าน แต่ไม่ใช่ในลักษณะที่ใช้หน่วยความจำ แม้ว่าข้อมูลจะถูกแคช แต่โอ้ ฉันเคยอ่านเรื่องนี้มาก่อนแล้วไม่ได้ถูกแคช - การอ่านแต่ละครั้งจะยังคงเติมบัฟเฟอร์โหมดผู้ใช้ในลักษณะเดียวกัน วิธีที่ข้อมูลเข้าถึงระบบไม่ควรสร้างความแตกต่าง [โปรดจำไว้ว่า OP กำลังใช้ recv หลังจากที่ระบบบอกว่าคุณมีข้อมูลที่ต้องจัดการ] โปรดบอกฉันว่าฉันคิดผิดตรงไหน? - person Mats Petersson; 30.07.2013

หากทุกสิ่งเท่ากัน หน่วยความจำก็คือหน่วยความจำ และไม่สำคัญว่าบัฟเฟอร์ของคุณจะอยู่บนฮีปหรือบนสแต็ก

แต่ชัดเจนว่าทุกสิ่งไม่เท่ากัน ฉันสงสัยว่าการจัดสรรบัฟเฟอร์ 1M บนสแต็ก INTERFERES/OVERLAPS กับพื้นที่สแต็กที่จัดสรรให้กับเธรด OTHER นั่นคือ การขยายสแต็กจำเป็นต้องย้ายสแต็กของเธรดปัจจุบัน หรือย้ายสแต็กของเธรดอื่น ต้องใช้เวลา เวลานี้ไม่จำเป็นเมื่อทำการจัดสรรจากฮีป หรือหากการจัดสรรสแต็กมีขนาดเล็กพอที่จะไม่รบกวน เช่นเดียวกับตัวอย่าง 1K

สมมติว่าคุณกำลังใช้งานเธรดที่เข้ากันได้กับ Posix ลองดูที่

pthread_create
pthread_attr_getstack
pthread_attr_setstack

เพื่อให้เธรดที่มีบัฟเฟอร์ 1M เพิ่มพื้นที่สแต็กในเวลาสร้างเธรด

-เจฟ

person Jeff N    schedule 29.07.2013
comment
ฉันยังสงสัยจุดนี้ด้วยและรีเซ็ตขนาดสแต็กกระบวนการเป็น 30M เพียงเพื่อจะพบว่ามันใช้งานไม่ได้ แอปพลิเคชันมีเธรดมากกว่า 60 เธรดที่ทำงานอยู่ - person Wallace; 31.07.2013

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

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

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

person Ben Voigt    schedule 30.07.2013