เทคนิคใดที่สามารถใช้เร่งความเร็วการรวบรวม C ++


249

เทคนิคใดที่สามารถใช้เร่งความเร็วการรวบรวม C ++

คำถามนี้เกิดขึ้นในความคิดเห็นบางส่วนเกี่ยวกับรูปแบบการเขียนโปรแกรมแบบซ้อนคำถามC ++และฉันสนใจที่จะรับฟังแนวคิดที่มีอยู่

ฉันเห็นคำถามที่เกี่ยวข้องแล้วเหตุใดการรวบรวม C ++ จึงใช้เวลานานมาก แต่นั่นไม่ได้มีวิธีแก้ปัญหามากมาย


1
คุณให้บริบทกับเราบ้างไหม หรือคุณกำลังมองหาคำตอบทั่วไปมาก ๆ ?
Pyrolistical

1
คล้ายกับคำถามนี้: stackoverflow.com/questions/364240/…
Adam Rosenfield

คำตอบทั่วไป ฉันมีฐานรหัสขนาดใหญ่มากที่เขียนโดยคนจำนวนมาก ความคิดเกี่ยวกับวิธีการโจมตีที่จะดี และคำแนะนำสำหรับการรวบรวมคอมไพล์อย่างรวดเร็วสำหรับโค้ดที่เขียนขึ้นใหม่นั้นน่าสนใจ
Scott Langham

โปรดสังเกตว่าบ่อยครั้งที่คอมไพเลอร์ไม่ได้ใช้งานคอมไพเลอร์ แต่เกี่ยวข้องกับการสร้างสคริปต์
thi gg

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

คำตอบ:


257

เทคนิคการใช้ภาษา

Pimpl Idiom

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

ไปข้างหน้าประกาศ

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

I / Oลำธารเป็นที่รู้จักกันโดยเฉพาะอย่างยิ่งสำหรับการชะลอตัวลงสร้าง หากคุณต้องการในไฟล์ส่วนหัวลอง #including <iosfwd>แทน<iostream>และ #include <iostream>ส่วนหัวในไฟล์การใช้งานเท่านั้น <iosfwd>หัวถือประกาศไปข้างหน้าเท่านั้น น่าเสียดายที่ส่วนหัวมาตรฐานอื่น ๆ ไม่มีส่วนหัวประกาศที่เกี่ยวข้อง

ชอบ pass-by-reference ถึง pass-by-value ในฟังก์ชั่นลายเซ็น สิ่งนี้จะช่วยลดความจำเป็นที่จะต้อง # รวมคำจำกัดความประเภทที่เกี่ยวข้องในไฟล์ส่วนหัวและคุณจะต้องประกาศล่วงหน้าประเภทเท่านั้น แน่นอนว่าต้องการอ้างอิง const ถึงการอ้างอิงที่ไม่ใช่ const เพื่อหลีกเลี่ยงข้อบกพร่องที่คลุมเครือ แต่นี่เป็นปัญหาสำหรับคำถามอื่น

สภาพความปลอดภัย

ใช้เงื่อนไขการป้องกันเพื่อป้องกันไม่ให้ไฟล์ส่วนหัวรวมอยู่มากกว่าหนึ่งครั้งในหน่วยการแปลเดียว

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

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

ลดการพึ่งพาซึ่งกันและกัน

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

ตัวเลือกคอมไพเลอร์

ส่วนหัวที่คอมไพล์แล้ว

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

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

ccacheเป็นอีกหนึ่งยูทิลิตี้ที่ใช้ประโยชน์จากเทคนิคการแคชเพื่อเร่งความเร็ว

ใช้ความเท่าเทียม

คอมไพเลอร์ / IDE หลายตัวรองรับการใช้หลายคอร์ / ซีพียูเพื่อทำการคอมไพล์พร้อมกัน ในGNU Make (มักใช้กับ GCC) ให้ใช้-j [N]ตัวเลือก ใน Visual Studio มีตัวเลือกภายใต้การตั้งค่าเพื่อให้สามารถสร้างหลายโครงการในแบบคู่ขนาน คุณยังสามารถใช้/MPตัวเลือกสำหรับ paralellism ระดับไฟล์แทนที่จะเป็นเพียง paralellism ระดับโครงการ

สาธารณูปโภคขนานอื่น ๆ :

ใช้ระดับการเพิ่มประสิทธิภาพที่ต่ำกว่า

ยิ่งคอมไพเลอร์พยายามเพิ่มประสิทธิภาพก็จะยิ่งทำงานได้ยากขึ้น

ไลบรารีที่แบ่งใช้

การย้ายรหัสที่แก้ไขน้อยลงไปยังห้องสมุดสามารถลดเวลาในการคอมไพล์ โดยใช้ไลบรารีที่แบ่งใช้ ( .soหรือ.dll) คุณสามารถลดเวลาในการลิงก์ได้เช่นกัน

รับคอมพิวเตอร์ที่เร็วขึ้น

RAM มากขึ้นฮาร์ดไดรฟ์ที่เร็วขึ้น (รวมถึง SSD) และ CPU / แกนเพิ่มเติมจะสร้างความแตกต่างในความเร็วในการรวบรวม


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

8
ในคอมไพเลอร์สมัยใหม่ #ifndef นั้นเร็วเท่า #pragma หนึ่งครั้ง (ตราบใดที่ตัวป้องกันรวมอยู่ที่ด้านบนของไฟล์) ดังนั้นจึงไม่มีประโยชน์ที่จะ #pragma ครั้งเดียวในแง่ของการรวบรวมความเร็ว
jalf

7
แม้ว่าคุณจะมีเพียง VS 2005 ไม่ใช่ 2008 คุณสามารถเพิ่ม / MP สวิตช์ในตัวเลือกการรวบรวมเพื่อเปิดใช้งานการสร้างแบบขนานที่ระดับ. cpp
macbirdie

6
SSD มีราคาแพงเมื่อเขียนคำตอบนี้ แต่วันนี้เป็นตัวเลือกที่ดีที่สุดเมื่อรวบรวม C ++ คุณเข้าถึงไฟล์ขนาดเล็กจำนวนมากเมื่อรวบรวม; ต้องใช้ IOPS จำนวนมากซึ่ง SSD มอบให้
MSalters

14
ชอบ pass-by-reference ถึง pass-by-value ในฟังก์ชั่นลายเซ็น นี้จะไม่จำเป็นต้อง # รวมคำนิยามชนิดเกี่ยวข้องในไฟล์ส่วนหัวนี้เป็นที่ไม่ถูกต้องคุณไม่จำเป็นต้องมีชนิดเต็มรูปแบบในการประกาศฟังก์ชั่นที่ผ่านโดยค่าที่คุณต้องการเพียงชนิดเต็มรูปแบบที่จะใช้หรือใช้ฟังก์ชั่นที่ แต่ในกรณีส่วนใหญ่ (ยกเว้นว่าคุณเป็นเพียงการส่งต่อสาย) คุณจะต้องใช้คำจำกัดความนั้น
David Rodríguez - dribeas

43

ฉันทำงานในโครงการ STAPL ซึ่งเป็นไลบรารี C ++ ที่มีเทมเพลตอย่างหนัก ในบางครั้งเราต้องทบทวนเทคนิคทั้งหมดเพื่อลดเวลาในการรวบรวม ที่นี่ฉันได้สรุปเทคนิคที่เราใช้ บางส่วนของเทคนิคเหล่านี้มีการระบุไว้ข้างต้น:

การค้นหาส่วนที่ใช้เวลานานที่สุด

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

วิธีที่ 1 - เรียงสัญลักษณ์ตามขนาด

คุณสามารถใช้nmคำสั่งเพื่อแสดงสัญลักษณ์ตามขนาดของมัน:

nm --print-size --size-sort --radix=d YOUR_BINARY

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

วิธีที่ 2 - เรียงสัญลักษณ์ตามความยาว

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

วิธีที่ 3 - ใช้ Templight

" Templightเป็นเครื่องมือที่ใช้Clangเพื่อทำโปรไฟล์การใช้เวลาและหน่วยความจำของอินสแตนซ์ของแม่แบบอินสแตนซ์และเพื่อดำเนินการเซสชันการดีบักแบบอินเทอร์แอคทีฟ

คุณสามารถติดตั้ง Templight โดยตรวจสอบLLVMและ Clang ( คำแนะนำ ) และใช้ Templight patch บน การตั้งค่าเริ่มต้นสำหรับ LLVM และ Clang นั้นอยู่ในการดีบักและการยืนยันและสิ่งเหล่านี้อาจส่งผลต่อเวลาในการรวบรวมของคุณอย่างมาก ดูเหมือนว่า Templight ต้องการทั้งสองอย่างดังนั้นคุณต้องใช้การตั้งค่าเริ่มต้น กระบวนการติดตั้ง LLVM และ Clang ควรใช้เวลาประมาณหนึ่งชั่วโมง

หลังจากใช้ชุดข้อมูลแก้ไขคุณสามารถใช้ตำแหน่งtemplight++ในโฟลเดอร์บิลด์ที่คุณระบุเมื่อทำการติดตั้งเพื่อคอมไพล์โค้ดของคุณ

ตรวจสอบให้แน่ใจว่าtemplight++อยู่ในเส้นทางของคุณ ตอนนี้เพื่อคอมไพล์เพิ่มสวิตช์ต่อไปนี้ของคุณCXXFLAGSใน Makefile หรือตัวเลือกบรรทัดคำสั่งของคุณ:

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

หรือ

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

หลังจากรวบรวมเสร็จแล้วคุณจะมี. trace.memory.pbf และ. trace.pbf สร้างขึ้นในโฟลเดอร์เดียวกัน หากต้องการเห็นภาพร่องรอยเหล่านี้คุณสามารถใช้เครื่องมือ Templightที่สามารถแปลงเป็นรูปแบบอื่นได้ ทำตามคำแนะนำเหล่านี้เพื่อติดตั้ง templight-conversion เรามักจะใช้การส่งออก callgrind คุณสามารถใช้เอาต์พุต GraphViz ได้หากโครงการของคุณมีขนาดเล็ก:

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

ไฟล์ callgrind ที่สร้างขึ้นสามารถเปิดได้โดยใช้kcachegrindซึ่งคุณสามารถติดตามการสร้างอินสแตนซ์ที่ใช้เวลามากที่สุด / หน่วยความจำ

ลดจำนวนอินสแตนซ์ของแม่แบบอินสแตนซ์

แม้ว่าจะไม่มีวิธีแก้ไขปัญหาที่แน่นอนสำหรับการลดจำนวนอินสแตนซ์ของแม่แบบ แต่ก็มีแนวทางเล็กน้อยที่สามารถช่วยได้:

คลาส Refactor ที่มีอาร์กิวเมนต์เท็มเพลตมากกว่าหนึ่งข้อ

ตัวอย่างเช่นถ้าคุณมีชั้นเรียน

template <typename T, typename U>
struct foo { };

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

Refactor โค้ดที่ไม่ใช่เท็มเพลตให้กับแต่ละหน่วยการแปล

การใช้เทคนิคนี้คุณสามารถรวบรวมส่วนทั่วไปหนึ่งครั้งและเชื่อมโยงกับ TU อื่น ๆ ของคุณ (หน่วยการแปล) ในภายหลัง

ใช้อินสแตนซ์แม่แบบ extern (ตั้งแต่ C ++ 11)

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

ตัวอย่างเช่นใน:

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

เรารู้ว่าคลาสนี้มีอินสแตนซ์ที่เป็นไปได้สามแบบ:

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

ใส่ค่าข้างต้นในหน่วยการแปลและใช้คำสำคัญ extern ในไฟล์ส่วนหัวของคุณด้านล่างคำจำกัดความของคลาส:

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

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

หมายเหตุ: MPICH2 ละเว้นการสร้างอินสแตนซ์ที่ชัดเจน ณ จุดนี้และคอมไพล์คลาสที่สร้างอินสแตนซ์ในหน่วยการคอมไพล์ทั้งหมดเสมอ

ใช้ความสามัคคีสร้าง

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

ตัวอย่างเช่นสมมติว่าคุณมีสามไฟล์foo1.cc, foo2.cc, foo3.ccและพวกเขาทั้งหมดรวมถึงtupleจากSTL คุณสามารถสร้างสิ่งfoo-all.ccที่ดูเหมือน:

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

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

นอกจากนี้หากไฟล์ใด ๆ เหล่านี้มีหน่วยความจำจำนวนมากคุณอาจมีหน่วยความจำไม่เพียงพอก่อนที่การรวบรวมจะจบ ในคอมไพเลอร์บางตัวเช่นGCCนี่อาจเป็น ICE (Internal Compiler Error) คอมไพเลอร์ของคุณเนื่องจากไม่มีหน่วยความจำ ดังนั้นอย่าใช้เทคนิคนี้จนกว่าคุณจะรู้ข้อดีและข้อเสียทั้งหมด

ส่วนหัวที่คอมไพล์แล้ว

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

$ g++ YOUR_HEADER.hpp

สิ่งนี้จะสร้างYOUR_HEADER.hpp.gch file( .gchเป็นส่วนขยายสำหรับไฟล์ PCH ใน GCC) ในโฟลเดอร์เดียวกัน ซึ่งหมายความว่าหากคุณรวมYOUR_HEADER.hppไว้ในไฟล์อื่น ๆ คอมไพเลอร์จะใช้ของคุณYOUR_HEADER.hpp.gchแทนYOUR_HEADER.hppในโฟลเดอร์เดียวกันก่อน

มีสองประเด็นด้วยเทคนิคนี้:

  1. คุณต้องตรวจสอบให้แน่ใจว่าไฟล์ส่วนหัวที่ถูกคอมไพล์ล่วงหน้านั้นเสถียรและจะไม่เปลี่ยนแปลง ( คุณสามารถเปลี่ยน makefile ของคุณได้เสมอ )
  2. คุณสามารถรวมหนึ่ง PCH ต่อหนึ่งหน่วยการรวบรวมเท่านั้น (ในคอมไพเลอร์ส่วนใหญ่) ซึ่งหมายความว่าหากคุณมีไฟล์ส่วนหัวมากกว่าหนึ่งไฟล์ที่จะทำการคอมไพล์ล่วงหน้าคุณจะต้องรวมไว้ในไฟล์เดียว (เช่นall-my-headers.hpp) แต่นั่นหมายความว่าคุณต้องรวมไฟล์ใหม่ในทุกที่ โชคดีที่ GCC มีทางออกสำหรับปัญหานี้ ใช้-includeและให้ไฟล์ส่วนหัวใหม่ คุณสามารถใช้เครื่องหมายจุลภาคคั่นไฟล์ต่างกันได้โดยใช้เทคนิคนี้

ตัวอย่างเช่น:

g++ foo.cc -include all-my-headers.hpp

ใช้เนมสเปซที่ไม่มีชื่อหรือไม่ระบุชื่อ

เนมสเปซที่ไม่มีชื่อ ( เนมสเปซที่ไม่ระบุชื่อ) สามารถลดขนาดไบนารีที่สร้างขึ้นได้อย่างมาก เนมสเปซที่ไม่มีชื่อใช้การเชื่อมโยงภายในซึ่งหมายความว่าสัญลักษณ์ที่สร้างขึ้นในเนมสเปซเหล่านั้นจะไม่ปรากฏแก่ TU อื่น (หน่วยการแปลหรือการรวบรวม) คอมไพเลอร์จะสร้างชื่อเฉพาะสำหรับเนมสเปซที่ไม่มีชื่อ ซึ่งหมายความว่าหากคุณมีไฟล์ foo.hpp:

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

และคุณได้รวมไฟล์นี้ในสอง TUs (ไฟล์. cc สองไฟล์และรวบรวมแยกต่างหาก) อินสแตนซ์ของแม่แบบ foo สองตัวจะไม่เหมือนกัน สิ่งนี้ละเมิดกฎข้อกำหนดหนึ่งข้อ (ODR) ด้วยเหตุผลเดียวกันการใช้เนมสเปซที่ไม่มีชื่อนั้นไม่ได้รับการสนับสนุนในไฟล์ส่วนหัว อย่าลังเลที่จะใช้มันใน.ccไฟล์ของคุณเพื่อหลีกเลี่ยงสัญลักษณ์ที่แสดงในไฟล์ไบนารีของคุณ ในบางกรณีการเปลี่ยนแปลงรายละเอียดภายในทั้งหมดสำหรับ.ccไฟล์แสดงให้เห็นถึงการลดลง 10% ในขนาดไบนารีที่สร้างขึ้น

การเปลี่ยนตัวเลือกการเปิดเผย

ในคอมไพเลอร์ที่ใหม่กว่าคุณสามารถเลือกสัญลักษณ์ของคุณเพื่อให้มองเห็นหรือมองไม่เห็นใน Dynamic Shared Objects (DSOs) เป็นการดีที่การเปลี่ยนการเปิดเผยสามารถปรับปรุงประสิทธิภาพของคอมไพเลอร์การเพิ่มประสิทธิภาพเวลาเชื่อมโยง (LTOs) และสร้างขนาดไบนารี ถ้าคุณดูไฟล์ส่วนหัว STL ใน GCC คุณจะเห็นว่ามันใช้กันอย่างแพร่หลาย ในการเปิดใช้งานตัวเลือกการเปิดเผยคุณต้องเปลี่ยนรหัสของคุณต่อฟังก์ชั่นต่อคลาสต่อตัวแปรและที่สำคัญกว่าต่อคอมไพเลอร์

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

หากคุณต้องการระบุความสามารถในการมองเห็นแต่ละกรณีคุณต้องเพิ่มคุณสมบัติต่อไปนี้ลงในฟังก์ชันตัวแปรและคลาสของคุณ:

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

การเปิดเผยค่าเริ่มต้นใน GCC เป็นค่าเริ่มต้น (สาธารณะ) ซึ่งหมายความว่าหากคุณรวบรวมข้างต้นเป็นวิธีการใช้ไลบรารีร่วมกัน ( -shared) foo2และคลาสfoo3จะไม่ปรากฏใน TU อื่น ๆ ( foo1และfoo4จะมองเห็นได้) หากคุณรวบรวม-visibility=hiddenแล้วเท่านั้นfoo1จะสามารถมองเห็น แม้foo4จะถูกซ่อนอยู่

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับการมองเห็นในGCC วิกิพีเดีย


33

ฉันขอแนะนำบทความเหล่านี้จาก "เกมจากภายในการออกแบบและการเขียนโปรแกรมเกมอินดี้":

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


17

เทคนิคหนึ่งที่ทำงานได้ค่อนข้างดีสำหรับฉันในอดีต: อย่ารวบรวมไฟล์ต้นฉบับ C ++ หลาย ๆ ไฟล์โดยอิสระ แต่ควรสร้างไฟล์ C ++ หนึ่งไฟล์ซึ่งรวมไฟล์อื่นทั้งหมดเช่นนี้

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

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

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

สำหรับสิ่งที่คุ้มค่าโครงการ KDEใช้เทคนิคเดียวกันนี้มาตั้งแต่ปี 1999 เพื่อสร้างไบนารีที่ปรับให้เหมาะสม --enable-finalสวิทช์ไปยังสคริปต์สร้างการกำหนดค่าที่เรียกว่า ฉันขุดขึ้นรายการซึ่งประกาศคุณลักษณะนี้: http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2


2
ฉันไม่แน่ใจว่าเป็นสิ่งเดียวกันหรือไม่ แต่ฉันคิดว่าการเปิดใช้ "การเพิ่มประสิทธิภาพโปรแกรมทั้งหมด" ใน VC ++ ( msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx ) ควรมี ผลเช่นเดียวกันกับประสิทธิภาพรันไทม์มากกว่าที่คุณแนะนำ เวลาในการรวบรวมอย่างไรจะดีกว่านี้ในแนวทางของคุณ!
Philipp

1
@Frerich: คุณกำลังอธิบายถึง Unity บิวด์ที่กล่าวถึงในคำตอบของ OJ ฉันเคยเห็นพวกเขาเรียกว่าการสร้างจำนวนมากและการสร้างต้นแบบ
idbrii

ดังนั้น UB จะเปรียบเทียบกับ WPO / LTCG อย่างไร
paulm

สิ่งนี้อาจมีประโยชน์สำหรับการรวบรวมแบบครั้งเดียวเท่านั้นไม่ใช่ในระหว่างการพัฒนาซึ่งคุณจะวนรอบระหว่างการแก้ไขการสร้างและการทดสอบ ในโลกสมัยใหม่สี่คอร์เป็นบรรทัดฐานบางทีอีกสองสามปีต่อมาการนับคอร์ก็ยิ่งมากขึ้น หากคอมไพเลอร์และลิงเกอร์ไม่สามารถใช้หลายเธรดได้ดังนั้นรายการของไฟล์อาจถูกแบ่งออกเป็น<core-count> + Nรายการย่อยที่คอมไพล์แบบขนานโดยNมีจำนวนเต็มที่เหมาะสม (ขึ้นอยู่กับหน่วยความจำระบบและการใช้เครื่อง)
FooF

15

มีหนังสือทั้งเล่มในหัวข้อนี้ซึ่งมีชื่อว่าLarge-Scale C ++ Software Design (เขียนโดย John Lakos)

เทมเพลตหนังสือล่วงหน้าเพื่อให้เนื้อหาของหนังสือเล่มนั้นเพิ่ม "โดยใช้เทมเพลตด้วยเช่นกันสามารถทำให้คอมไพเลอร์ช้าลง"


หนังสือเล่มนี้มักถูกอ้างถึงในหัวข้อประเภทนี้ แต่สำหรับฉันมันมีข้อมูลน้อยมาก โดยทั่วไปจะใช้การประกาศล่วงหน้ามากที่สุดเท่าที่จะเป็นไปได้และลดการพึ่งพา เป็นบิตที่ระบุชัดเจนนอกเหนือจากการใช้ pimpl idiom มีข้อบกพร่อง runtime
gast128

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

15

ฉันจะเชื่อมโยงไปยังคำตอบอื่น ๆ ของฉัน: คุณจะลดเวลาการคอมไพล์และเวลาเชื่อมโยงสำหรับโครงการ Visual C ++ (เนทีฟ C ++) ได้อย่างไร? . อีกจุดที่ฉันต้องการเพิ่ม แต่สิ่งที่ทำให้เกิดปัญหาบ่อยครั้งคือการใช้ส่วนหัวที่คอมไพล์แล้ว แต่โปรดใช้เฉพาะชิ้นส่วนที่แทบจะไม่เปลี่ยนแปลง (เช่นส่วนหัวของชุดเครื่องมือ GUI) มิฉะนั้นพวกเขาจะเสียเวลามากกว่าที่คุณประหยัดในที่สุด

ตัวเลือกอื่นคือเมื่อคุณทำงานกับ GNU make เพื่อเปิด-j<N>ตัวเลือก:

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

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

แต่ตัวเชื่อมโยงสามารถทำเกลียวได้และนี่คือสิ่งที่ตัวเชื่อมโยงของELFทำ มันเป็นรหัส C ++ ที่ได้รับการปรับให้เหมาะสมซึ่งกล่าวกันว่าลิงก์ไฟล์วัตถุ ELF มีขนาดที่เร็วกว่าเก่า(และรวมอยู่ในbinutils )GNU gold ld


โอเคใช่. ขออภัยที่คำถามไม่ได้เกิดขึ้นเมื่อฉันค้นหา
Scott Langham

คุณไม่ต้องเสียใจ นั่นคือสำหรับ Visual C ++ คำถามของคุณดูเหมือนจะเป็นคอมไพเลอร์ใด ๆ ก็ไม่เป็นไร :)
Johannes Schaub - litb

12

นี่คือบางส่วน:

  • ใช้แกนประมวลผลทั้งหมดโดยเริ่มงานที่รวบรวมหลาย ๆ ( make -j2เป็นตัวอย่างที่ดี)
  • ปิดหรือลดการเพิ่มประสิทธิภาพ (ตัวอย่างเช่น GCC เร็ว-O1กว่า-O2หรือ-O3มาก)
  • ใช้ส่วนหัวที่คอมไพล์แล้ว

12
FYI ฉันพบว่าโดยปกติจะเร็วกว่าในการเริ่มต้นกระบวนการมากกว่าแกนประมวลผล ตัวอย่างเช่นในระบบ quad core ฉันมักจะใช้ -j8 ไม่ใช่ -j4 เหตุผลนี้คือเมื่อกระบวนการหนึ่งถูกบล็อกบน I / O กระบวนการอื่นสามารถรวบรวมได้
Mr Fooz

@MrFooz: ฉันทดสอบนี้ไม่กี่ปีที่ผ่านมาโดยการรวบรวมเคอร์เนล Linux (จากการจัดเก็บ RAM) ใน i7-2700k (4 คอร์, 8 กระทู้ฉันตั้งตัวคูณคงที่) ฉันลืมผลลัพธ์ที่ดีที่สุด แต่-j12ไปรอบ ๆ-j18นั้นเร็วกว่า-j8ที่คุณแนะนำ ฉันสงสัยว่าคุณสามารถมีกี่คอร์ก่อนแบนด์วิดท์หน่วยความจำจะกลายเป็นปัจจัย จำกัด ...
Mark K Cowan

@ MarkKCowan มันขึ้นอยู่กับปัจจัยหลายอย่าง คอมพิวเตอร์แต่ละเครื่องมีแบนด์วิดท์หน่วยความจำที่แตกต่างกัน ด้วยโปรเซสเซอร์ระดับสูงในทุกวันนี้มันต้องใช้หลายคอร์ในการอิ่มตัวบัสหน่วยความจำ นอกจากนี้ยังมีความสมดุลระหว่าง I / O และ CPU โค้ดบางตัวนั้นง่ายต่อการคอมไพล์โค้ดอื่น ๆ อาจช้า (เช่นมีเทมเพลตจำนวนมาก) กฎง่ายๆของฉันในปัจจุบันคือ-j2x กับจำนวนแกนที่แท้จริง
Mr Fooz

11

เมื่อคุณใช้เคล็ดลับรหัสทั้งหมดข้างต้น (การประกาศไปข้างหน้าลดการรวมส่วนหัวให้น้อยที่สุดในส่วนหัวสาธารณะผลักรายละเอียดส่วนใหญ่ในไฟล์การนำไปใช้งานด้วยPimpl ... ) และไม่มีอะไรอื่นที่สามารถรับภาษาได้ . หากคุณใช้ Linux ให้พิจารณาใช้distcc (compiler แบบกระจาย) และccache (cache compiler)

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

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

ทั้งสองสามารถใช้งานได้ในเวลาเดียวกันดังนั้นหาก ccache ไม่มีสำเนาภายในเครื่องก็สามารถส่งสุทธิไปยังโหนดอื่นด้วย distcc หรือมิฉะนั้นก็สามารถฉีดสารละลายโดยไม่ต้องดำเนินการเพิ่มเติม


2
ฉันไม่คิดว่า distcc ที่ต้องเดียวกันห้องสมุดรุ่นบนโหนดการกำหนดค่าทั้งหมด distcc จะรวบรวมจากระยะไกลเท่านั้นไม่ใช่การเชื่อมโยง นอกจากนี้ยังส่งรหัสที่ประมวลผลล่วงหน้าผ่านสายเพื่อให้ส่วนหัวที่มีอยู่ในระบบระยะไกลไม่สำคัญ
Frerich Raabe

9

เมื่อฉันออกจากวิทยาลัยรหัส C ++ ที่คุ้มค่ากับการผลิตครั้งแรกที่ฉันได้เห็นมี #ifndef arcane เหล่านี้ ... #endif คำสั่งระหว่างพวกเขาที่ส่วนหัวถูกกำหนดไว้ ฉันถามคนที่เขียนรหัสเกี่ยวกับสิ่งที่ครอบคลุมเหล่านี้ในแบบไร้เดียงสามากและได้รับการแนะนำให้รู้จักกับโลกแห่งการเขียนโปรแกรมขนาดใหญ่

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


1
เก่า แต่มีคุณค่า. บางครั้งความชัดเจนถูกลืม
Alcor

1
'include guards'
gast128

8

RAM เพิ่มเติม

มีคนพูดถึง RAM ไดรฟ์ในคำตอบอื่น ฉันทำสิ่งนี้ด้วย80286และTurbo C ++ (แสดงอายุ) และผลลัพธ์ก็น่าอัศจรรย์ เช่นเดียวกับการสูญเสียข้อมูลเมื่อเครื่องขัดข้อง


ใน DOS คุณมีหน่วยความจำไม่มากนัก
phuclv

6

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

ตัวอย่างเช่น:

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

จำนวนที่น้อยลงหมายถึงการทำงานที่น้อยลงสำหรับตัวประมวลผลล่วงหน้าถ้าคุณทำเพียงพอ


สิ่งนี้ไม่สำคัญเฉพาะเมื่อส่วนหัวเดียวกันรวมอยู่ในหลายหน่วยการแปลหรือไม่ หากมีหน่วยการแปลเพียงหน่วยเดียว (ตามปกติในกรณีที่มีการใช้เทมเพลต) สิ่งนี้ดูเหมือนจะไม่มีผลกระทบใด ๆ
AlwaysLearning

1
หากมีหน่วยการแปลเพียงหน่วยเดียวเหตุใดจึงต้องใส่ไว้ในส่วนหัว การใส่เนื้อหาลงในไฟล์ต้นฉบับจะไม่เหมาะสมกว่านี้ไหม จุดรวมทั้งหมดของส่วนหัวนั้นไม่น่าจะรวมอยู่ในไฟล์ต้นฉบับมากกว่าหนึ่งไฟล์ใช่ไหม
Evan Teran


5

ใช้

#pragma once

ที่ด้านบนของไฟล์ส่วนหัวดังนั้นหากมีการรวมมากกว่าหนึ่งครั้งในหน่วยการแปลข้อความของส่วนหัวจะถูกรวมและแยกวิเคราะห์เพียงครั้งเดียว


2
แม้ว่าได้รับการสนับสนุนอย่างกว้างขวาง #pragma หนึ่งครั้งนั้นไม่ได้มาตรฐาน ดูen.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton

7
และในทุกวันนี้เจ้าหน้าที่รักษาความปลอดภัยประจำมีผลเหมือนกัน ตราบใดที่พวกเขาอยู่ที่ด้านบนของไฟล์คอมไพเลอร์เป็นอย่างที่สามารถรักษาพวกเขาเป็น #pragma ครั้งเดียว
jalf

5

เพื่อความสมบูรณ์: บิลด์อาจช้าเพราะระบบบิลด์นั้นโง่และคอมไพเลอร์ใช้เวลานานในการทำงาน

อ่านRecursive Make พิจารณาว่าเป็นอันตราย (PDF) สำหรับการอภิปรายหัวข้อนี้ในสภาพแวดล้อม Unix


4
  • อัพเกรดคอมพิวเตอร์ของคุณ

    1. รับ quad core (หรือระบบ dual-quad)
    2. รับ RAM มากมาย
    3. ใช้ RAM drive เพื่อลดความล่าช้าของ I / O ไฟล์ลงอย่างมาก (มี บริษัท ที่สร้างไดรฟ์ IDE และ SATA RAM ที่ทำหน้าที่เหมือนฮาร์ดไดรฟ์)
  • จากนั้นคุณมีข้อเสนอแนะทั่วไปอื่น ๆ ของคุณ

    1. ใช้ส่วนหัว precompiled ถ้ามี
    2. ลดปริมาณการเชื่อมต่อระหว่างส่วนต่าง ๆ ของโครงการของคุณ โดยปกติแล้วการเปลี่ยนไฟล์ส่วนหัวหนึ่งไฟล์ไม่ควรต้องมีการคอมไพล์โครงการใหม่ทั้งหมดของคุณ

4

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


ฮะ. ทำไมบางคนลงคะแนนนี้ พรุ่งนี้ฉันจะลองดู
Scott Langham

1
ฉันคาดหวังว่าการลงคะแนนเสียงนั้นเป็นเพราะไม่เคยสร้างความแตกต่างใหญ่โต หากคุณมี RAM ที่ไม่ได้ใช้อย่างเพียงพอระบบปฏิบัติการจะใช้มันเป็นแคชดิสก์อย่างชาญฉลาด
MSalters

1
@MSalters - และ "เพียงพอ" เท่าไหร่ ฉันรู้ว่านั่นเป็นทฤษฎี แต่ด้วยเหตุผลบางอย่างที่ใช้ RAMdrive นั้นให้การสนับสนุนที่สำคัญ ไปคิด ...
Vilx-

1
พอที่จะรวบรวมโครงการของคุณและยังคงแคชอินพุตและไฟล์ชั่วคราว เห็นได้ชัดว่าด้านใน GB จะขึ้นอยู่กับขนาดโครงการของคุณโดยตรง ควรสังเกตว่าในระบบปฏิบัติการรุ่นเก่า (โดยเฉพาะ WinXP) ไฟล์แคชค่อนข้างขี้เกียจทำให้ RAM ไม่ได้ใช้งาน
MSalters

หน่วยความจำไดรฟ์จะเร็วกว่าถ้าไฟล์นั้นมีอยู่ในหน่วยความจำแทนที่จะทำทั้งกลุ่มของ IO ช้าก่อนแล้วพวกเขาก็อยู่ในหน่วยความจำ? (เพิ่มขึ้นซ้ำสำหรับไฟล์ที่มีการเปลี่ยนแปลง - เขียนมันกลับไปที่ดิสก์ ฯลฯ )
paulm

3

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


การเชื่อมโยงแบบไดนามิกป้องกันการเพิ่มประสิทธิภาพการเชื่อมโยงเวลาหลายประเภทดังนั้นผลลัพธ์อาจช้าลงในหลายกรณี
27432

3

ไม่เกี่ยวกับเวลารวบรวม แต่เกี่ยวกับเวลาสร้าง:

  • ใช้ccacheหากคุณต้องสร้างไฟล์เดียวกันเมื่อคุณทำงานกับ buildfiles ของคุณ

  • ใช้นินจาสร้างแทนการสร้าง ขณะนี้ฉันกำลังรวบรวมโครงการด้วยซอร์สไฟล์ ~ 100 ไฟล์และทุกอย่างถูกแคชด้วย ccache ทำให้ความต้องการ 5 นาทีนินจาน้อยกว่า 1

คุณสามารถสร้างนินจาไฟล์ของคุณจาก CMake -GNinjaกับ


3

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

ภายใต้ gcc / g ++ คุณดูที่ccacheหรือไม่ มันจะมีประโยชน์ถ้าคุณทำmake clean; makeมาก


2

ฮาร์ดดิสก์เร็วขึ้น

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



2

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


2

หากคุณมีตัวประมวลผลแบบมัลติคอร์ทั้ง Visual Studio (2005 และใหม่กว่า) เช่นเดียวกับGCCรองรับการประมวลผลแบบหลายตัวประมวลผล เป็นสิ่งที่เปิดใช้งานหากคุณมีฮาร์ดแวร์แน่นอน


2
@Fellman ดูคำตอบอื่น ๆ - ใช้ตัวเลือก -j #
แปลกหน้า

1

แม้ว่าจะไม่ใช่ "เทคนิค" แต่ฉันไม่สามารถเข้าใจได้ว่าโครงการ Win32 ที่มีไฟล์ต้นฉบับมากมายรวบรวมได้เร็วกว่าโครงการเปล่า "Hello World" ของฉัน ดังนั้นฉันหวังว่าสิ่งนี้จะช่วยให้คนที่ฉันทำ

ใน Visual Studio หนึ่งตัวเลือกเพื่อเพิ่มเวลาในการคอมไพล์คือการเชื่อมโยงที่เพิ่มขึ้น ( / INCREMENTAL ) มันเข้ากันไม่ได้กับการสร้างรหัสลิงค์เวลา ( / LTCG ) ดังนั้นอย่าลืมปิดการเชื่อมโยงที่เพิ่มขึ้นเมื่อทำการสร้างรุ่น


1
การปิดใช้งานการสร้างรหัสลิงค์เวลาไม่ใช่คำแนะนำที่ดีเนื่องจากจะปิดใช้งานการปรับแต่งมากมาย คุณต้องเปิดใช้งาน/INCREMENTALในโหมดดีบักเท่านั้น
phuclv

1

เริ่มต้นด้วย Visual Studio 2017 คุณมีความสามารถในการมีตัวชี้วัดของคอมไพเลอร์เกี่ยวกับสิ่งที่ต้องใช้เวลา

เพิ่มพารามิเตอร์เหล่านั้นใน C / C ++ -> บรรทัดคำสั่ง (ตัวเลือกเพิ่มเติม) ในหน้าต่างคุณสมบัติโครงการ: /Bt+ /d2cgsummary /d1reportTime

คุณสามารถมีข้อมูลเพิ่มเติมในโพสต์นี้


0

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

หากคุณใช้ t Cmake ให้เปิดใช้งานคุณสมบัติ:

set(BUILD_SHARED_LIBS ON)

Build Release โดยใช้การลิงก์แบบสแตติกสามารถรับการเพิ่มประสิทธิภาพได้มากขึ้น

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