C # vs C - ความแตกต่างของประสิทธิภาพที่ยิ่งใหญ่


94

ฉันพบความแตกต่างของประสิทธิภาพอย่างมากระหว่างโค้ดที่คล้ายกันใน C An C #

รหัส C คือ:

#include <stdio.h>
#include <time.h>
#include <math.h>

main()
{
    int i;
    double root;

    clock_t start = clock();
    for (i = 0 ; i <= 100000000; i++){
        root = sqrt(i);
    }
    printf("Time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);   

}

และ C # (แอปคอนโซล) คือ:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;
            double root;
            for (int i = 0; i <= 100000000; i++)
            {
                root = Math.Sqrt(i);
            }
            TimeSpan runTime = DateTime.Now - startTime;
            Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds/1000));
        }
    }
}

ด้วยรหัสข้างต้น C # จะเสร็จสมบูรณ์ใน 0.328125 วินาที (รุ่นวางจำหน่าย) และ C ใช้เวลา 11.14 วินาทีในการทำงาน

c กำลังถูกคอมไพล์ไปยัง windows ที่เรียกใช้งานได้โดยใช้ mingw

ฉันอยู่ภายใต้สมมติฐานเสมอว่า C / C ++ เร็วกว่าหรืออย่างน้อยก็เทียบได้กับ C # .net อะไรคือสาเหตุที่ทำให้ C ทำงานช้าลงกว่า 30 เท่า?

แก้ไข: ดูเหมือนว่าเครื่องมือเพิ่มประสิทธิภาพ C # กำลังลบรูทเนื่องจากไม่ได้ใช้งาน ฉันเปลี่ยนการกำหนดรูทเป็นรูท + = และพิมพ์ผลรวมในตอนท้าย ฉันยังรวบรวม C โดยใช้ cl.exe พร้อมกับตั้งค่าสถานะ / O2 สำหรับความเร็วสูงสุด

ผลลัพธ์ตอนนี้: 3.75 วินาทีสำหรับ C 2.61 วินาทีสำหรับ C #

C ยังคงใช้เวลานานกว่า แต่ก็ยอมรับได้


19
ฉันขอแนะนำให้คุณใช้ StopWatch แทน DateTime
Alex Fort

2
คอมไพเลอร์แฟล็กใด ทั้งสองคอมไพล์ด้วยการเพิ่มประสิทธิภาพเปิดใช้งานหรือไม่
jalf

2
แล้วเมื่อคุณใช้ -ffast-math กับคอมไพเลอร์ C ++ ล่ะ?
Dan McClain

10
ช่างเป็นคำถามที่น่าสนใจ!
Robert S.

4
บางทีฟังก์ชัน C sqrt จะไม่ดีเท่านี้ใน C # ถ้าอย่างนั้นมันจะไม่เป็นปัญหากับ C แต่มีไลบรารีที่แนบมาด้วย ลองคำนวณโดยไม่มีฟังก์ชันทางคณิตศาสตร์
klew

คำตอบ:


61

เนื่องจากคุณไม่เคยใช้ 'root' คอมไพลเลอร์อาจลบการโทรออกเพื่อปรับวิธีการของคุณให้เหมาะสม

คุณสามารถลองสะสมค่ารากที่สองลงในตัวสะสมพิมพ์ออกมาที่ส่วนท้ายของวิธีการและดูว่าเกิดอะไรขึ้น

แก้ไข: ดูคำตอบของ Jalfด้านล่าง


1
การทดลองเล็กน้อยแสดงให้เห็นว่าไม่เป็นเช่นนั้น รหัสสำหรับลูปถูกสร้างขึ้นแม้ว่ารันไทม์อาจฉลาดพอที่จะข้ามไปได้ แม้จะสะสม C # ก็ยังเต้นกางเกงของ C.
Dana

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

2
พวกคุณหลายคนคิดถึงจุดนี้ ฉันได้อ่านกรณีที่คล้ายกันมากมายที่ c # มีประสิทธิภาพดีกว่า c / c ++ และการโต้แย้งเสมอคือการใช้การเพิ่มประสิทธิภาพระดับผู้เชี่ยวชาญ 99% ของโปรแกรมเมอร์ไม่มีความรู้ในการใช้เทคนิคการเพิ่มประสิทธิภาพดังกล่าวเพียงเพื่อให้โค้ดทำงานเร็วกว่าโค้ด c # เล็กน้อย กรณีการใช้งานสำหรับ c / c ++ จะแคบลง

167

คุณต้องเปรียบเทียบบิวด์ดีบัก ฉันเพิ่งรวบรวมรหัส C ของคุณและได้รับ

Time elapsed: 0.000000

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

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

อย่างไรก็ตามคุณจะไม่ต้องปวดหัวมากในระยะยาวหากคุณละทิ้งความคิดที่ว่าภาษา "เร็ว" กว่าภาษาอื่น ๆ C # ไม่มีความเร็วมากกว่าภาษาอังกฤษ

มีบางสิ่งในภาษา C ที่จะมีประสิทธิภาพแม้ในคอมไพเลอร์ที่ไม่ได้ปรับแต่งที่ไร้เดียงสาและยังมีสิ่งอื่น ๆ ที่ต้องอาศัยคอมไพเลอร์เป็นอย่างมากในการปรับทุกอย่างให้เหมาะสม และแน่นอนเช่นเดียวกันกับ C # หรือภาษาอื่น ๆ

ความเร็วในการดำเนินการกำหนดโดย:

  • แพลตฟอร์มที่คุณใช้งานอยู่ (ระบบปฏิบัติการฮาร์ดแวร์ซอฟต์แวร์อื่น ๆ ที่ทำงานบนระบบ)
  • คอมไพเลอร์
  • ซอร์สโค้ดของคุณ

คอมไพเลอร์ C # ที่ดีจะให้รหัสที่มีประสิทธิภาพ คอมไพเลอร์ C ที่ไม่ดีจะสร้างโค้ดช้า แล้วคอมไพเลอร์ C ที่สร้างรหัส C # ซึ่งคุณสามารถเรียกใช้ผ่านคอมไพเลอร์ C # ได้ล่ะ? จะวิ่งเร็วแค่ไหน? ภาษาไม่มีความเร็ว รหัสของคุณไม่


อ่านที่น่าสนใจอีกมากมายที่นี่: blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx
Daniel Earwicker

18
คำตอบที่ดี แต่ฉันไม่เห็นด้วยกับความเร็วของภาษาอย่างน้อยก็ในการเปรียบเทียบ: พบว่า Welsch เป็นภาษาที่ช้ากว่าส่วนใหญ่เนื่องจากมีความถี่สูงของเสียงสระยาว นอกจากนี้ผู้คนจำคำศัพท์ (และรายการคำศัพท์) ได้ดีขึ้นหากพูดได้เร็วขึ้น web.missouri.edu/~cowann/docs/articles/before%201993/… th.wikipedia.org/wiki/Vowel_length en.wikipedia.org/wiki/Welsh_language
ข้อยกเว้นข้อผิดพลาด

1
นั่นไม่ได้ขึ้นอยู่กับสิ่งที่คุณพูดใน Welsch เหรอ? ฉันคิดว่ามันไม่น่าเป็นไปได้ที่ทุกอย่างจะช้าลง
jalf

5
++ เฮ้พวกเธออย่ามาขวางทางนี้ หากโปรแกรมเดียวกันทำงานเร็วกว่าในภาษาหนึ่งมากกว่าอีกภาษาหนึ่งนั่นเป็นเพราะมีการสร้างรหัสแอสเซมบลีที่แตกต่างกัน ในตัวอย่างนี้เวลา 99% ขึ้นไปจะลอยตัวiและsqrtนั่นคือสิ่งที่วัดได้
Mike Dunlavey

116

ฉันจะสรุปสั้น ๆ ว่ามีคำตอบแล้ว C # มีข้อได้เปรียบอย่างมากในการมีแบบจำลองจุดลอยตัวที่กำหนดไว้อย่างดี สิ่งนี้เกิดขึ้นเพื่อให้ตรงกับโหมดการทำงานดั้งเดิมของชุดคำสั่ง FPU และ SSE บนโปรเซสเซอร์ x86 และ x64 ไม่มีความบังเอิญที่นั่น JITter รวบรวม Math.Sqrt () เป็นคำสั่งแบบอินไลน์สองสามคำสั่ง

Native C / C ++ มีความเข้ากันได้ย้อนหลังเป็นเวลาหลายปี / fp: precision, / fp: fast และ / fp: ตัวเลือกการคอมไพล์ที่เข้มงวดจะมองเห็นได้มากที่สุด ดังนั้นจึงต้องเรียกใช้ฟังก์ชัน CRT ที่ใช้ sqrt () และตรวจสอบตัวเลือกทศนิยมที่เลือกเพื่อปรับผลลัพธ์ ช้าจัง


67
นี่เป็นความเชื่อมั่นแปลก ๆ ในหมู่โปรแกรมเมอร์ C ++ พวกเขาดูเหมือนจะคิดว่ารหัสเครื่องที่สร้างโดย C # นั้นแตกต่างจากรหัสเครื่องที่สร้างโดยคอมไพเลอร์ดั้งเดิม มีเพียงชนิดเดียว ไม่ว่าคุณจะใช้สวิตช์คอมไพเลอร์ gcc หรือแอสเซมบลีแบบอินไลน์ที่คุณเขียนยังมีคำสั่ง FSQRT เพียงคำสั่งเดียว มันไม่ได้เร็วขึ้นเสมอไปเพราะภาษาแม่สร้างมันขึ้นมาซีพียูไม่สนใจ
Hans Passant

17
นั่นคือสิ่งที่แก้ไขล่วงหน้าด้วย ngen.exe เรากำลังพูดถึง C # ไม่ใช่ Java
Hans Passant

21
@ user877329 - จริงเหรอ? ว้าว.
Andras Zoltan

8
ไม่กระวนกระวายใจ x64 ใช้ SSE Math.Sqrt () ได้รับการแปลเป็นคำสั่งรหัสเครื่อง sqrtsd
Hans Passant

6
แม้ว่าในทางเทคนิคจะไม่ใช่ความแตกต่างระหว่างภาษา แต่. net JITter จะทำการปรับให้เหมาะสมค่อนข้าง จำกัด เมื่อเทียบกับคอมไพเลอร์ C / C ++ ทั่วไป ข้อ จำกัด ที่ใหญ่ที่สุดประการหนึ่งคือการขาดการรองรับ SIMD ทำให้โค้ดมักช้าลงประมาณ 4 เท่า การไม่เปิดเผยสิ่งที่อยู่ภายในมากมายอาจเป็นผลร้ายได้เช่นกัน แต่ขึ้นอยู่กับสิ่งที่คุณกำลังทำอยู่มาก
CodesInChaos

57

ฉันเป็น C ++ และนักพัฒนา C # ฉันได้พัฒนาแอปพลิเคชั่น C # ตั้งแต่เบต้าแรกของเฟรมเวิร์ก. NET และฉันมีประสบการณ์มากกว่า 20 ปีในการพัฒนาแอปพลิเคชัน C ++ ประการแรกรหัส C # จะไม่เร็วกว่าแอปพลิเคชัน C ++ แต่ฉันจะไม่พูดถึงการสนทนาที่ยาวนานเกี่ยวกับโค้ดที่มีการจัดการวิธีการทำงานเลเยอร์ระหว่างการทำงานการจัดการหน่วยความจำภายในระบบประเภทไดนามิกและตัวรวบรวมขยะ อย่างไรก็ตามให้ฉันดำเนินการต่อโดยบอกว่าเกณฑ์มาตรฐานที่ระบุไว้ที่นี่ทั้งหมดให้ผลลัพธ์ที่ไม่ถูกต้อง

ให้ฉันอธิบาย: สิ่งแรกที่เราต้องพิจารณาคือคอมไพเลอร์ JIT สำหรับ C # (.NET Framework 4) ตอนนี้ JIT สร้างโค้ดเนทีฟสำหรับ CPU โดยใช้อัลกอริธึมการเพิ่มประสิทธิภาพต่างๆ (ซึ่งมีแนวโน้มที่จะก้าวร้าวมากกว่าเครื่องมือเพิ่มประสิทธิภาพ C ++ เริ่มต้นที่มาพร้อมกับ Visual Studio) และชุดคำสั่งที่ใช้โดยคอมไพลเลอร์. NET JIT เป็นการสะท้อนที่ใกล้เคียงกับ CPU จริงมากขึ้น บนเครื่องดังนั้นการแทนที่บางอย่างในรหัสเครื่องสามารถทำได้เพื่อลดรอบนาฬิกาและปรับปรุงอัตราการตีในแคชไปป์ไลน์ของ CPU และสร้างการเพิ่มประสิทธิภาพไฮเปอร์เธรดเพิ่มเติมเช่นคำสั่งของเราในการจัดลำดับใหม่และการปรับปรุงที่เกี่ยวข้องกับการทำนายสาขา

สิ่งนี้หมายความว่าเว้นแต่คุณจะคอมไพล์แอปพลิเคชัน C ++ ของคุณโดยใช้พารามิเตอรที่ถูกต้องสำหรับรุ่น RELEASE (ไม่ใช่รุ่น DEBUG) แอปพลิเคชัน C ++ ของคุณอาจทำงานช้ากว่าแอปพลิเคชัน C # หรือ. NET ที่เกี่ยวข้อง เมื่อระบุคุณสมบัติโปรเจ็กต์บนแอปพลิเคชัน C ++ ของคุณตรวจสอบให้แน่ใจว่าคุณเปิดใช้งาน "การเพิ่มประสิทธิภาพเต็มรูปแบบ" และ "โปรดปรานโค้ดด่วน" หากคุณมีเครื่อง 64 บิตคุณต้องระบุเพื่อสร้าง x64 เป็นแพลตฟอร์มเป้าหมายมิฉะนั้นโค้ดของคุณจะถูกเรียกใช้ผ่านเลเยอร์ย่อยการแปลง (WOW64) ซึ่งจะลดประสิทธิภาพลงอย่างมาก

เมื่อคุณดำเนินการปรับแต่งที่ถูกต้องในคอมไพเลอร์ฉันจะได้รับ. 72 วินาทีสำหรับแอปพลิเคชัน C ++ และ 1.16 วินาทีสำหรับแอปพลิเคชัน C # (ทั้งในรุ่นรุ่น) เนื่องจากแอปพลิเคชัน C # เป็นแอปพลิเคชันพื้นฐานมากและจัดสรรหน่วยความจำที่ใช้ในการวนซ้ำบนสแต็กและไม่ได้อยู่บนฮีปจึงทำงานได้ดีกว่าแอปพลิเคชันจริงที่เกี่ยวข้องกับวัตถุการคำนวณที่หนักหน่วงและชุดข้อมูลขนาดใหญ่ ดังนั้นตัวเลขที่ให้จึงเป็นตัวเลขในแง่ดีที่เอนเอียงไปทาง C # และกรอบงาน. NET แม้จะมีอคตินี้ แต่แอปพลิเคชัน C ++ ก็ทำงานเสร็จในเวลาเพียงครึ่งเวลากว่าแอปพลิเคชัน C # ที่เทียบเท่า โปรดทราบว่าคอมไพเลอร์ Microsoft C ++ ที่ฉันใช้ไม่มีการเพิ่มประสิทธิภาพไปป์ไลน์และไฮเปอร์เธรดที่ถูกต้อง (โดยใช้ WinDBG เพื่อดูคำแนะนำในการประกอบ)

ตอนนี้ถ้าเราใช้คอมไพเลอร์ Intel (ซึ่งเป็นความลับของอุตสาหกรรมในการสร้างแอปพลิเคชันประสิทธิภาพสูงบนโปรเซสเซอร์ AMD / Intel) รหัสเดียวกันจะทำงานใน. 54 วินาทีสำหรับปฏิบัติการ C ++ เทียบกับ. 72 วินาทีโดยใช้ Microsoft Visual Studio 2010 ท้ายที่สุดผลลัพธ์สุดท้ายคือ. 54 วินาทีสำหรับ C ++ และ 1.16 วินาทีสำหรับ C # ดังนั้นโค้ดที่สร้างโดยคอมไพลเลอร์. NET JIT จึงใช้เวลานานกว่าไฟล์ปฏิบัติการ C ++ ถึง 214% เวลาส่วนใหญ่ที่ใช้ใน. 54 วินาทีคือการรับเวลาจากระบบและไม่ได้อยู่ในลูปนั่นเอง!

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

เมื่อวัดประสิทธิภาพของ C ++ และ. NET IL สิ่งสำคัญคือต้องดูรหัสแอสเซมบลีเพื่อให้แน่ใจว่ามีการคำนวณทั้งหมดอยู่ที่นั่น สิ่งที่ฉันพบคือการไม่ใส่โค้ดเพิ่มเติมใน C # โค้ดส่วนใหญ่ในตัวอย่างด้านบนจะถูกลบออกจากไบนารี นี่เป็นกรณีของ C ++ เช่นกันเมื่อคุณใช้เครื่องมือเพิ่มประสิทธิภาพเชิงรุกเช่นตัวที่มาพร้อมกับคอมไพเลอร์ Intel C ++ ผลลัพธ์ที่ฉันให้ไว้ข้างต้นถูกต้อง 100% และผ่านการตรวจสอบในระดับการประกอบ

ปัญหาหลักของฟอรัมจำนวนมากบนอินเทอร์เน็ตที่มีมือใหม่จำนวนมากฟังโฆษณาชวนเชื่อทางการตลาดของ Microsoft โดยไม่เข้าใจเทคโนโลยีและอ้างว่า C # เร็วกว่า C ++ ข้ออ้างคือตามทฤษฎีแล้ว C # เร็วกว่า C ++ เนื่องจากคอมไพเลอร์ JIT สามารถปรับโค้ดให้เหมาะสมกับ CPU ได้ ปัญหาของทฤษฎีนี้คือมีท่อประปาจำนวนมากที่มีอยู่ในกรอบงาน. NET ที่ทำให้ประสิทธิภาพการทำงานช้าลง ท่อประปาที่ไม่มีในแอปพลิเคชัน C ++ นอกจากนี้นักพัฒนาที่มีประสบการณ์จะรู้จักคอมไพเลอร์ที่เหมาะสมที่จะใช้สำหรับแพลตฟอร์มที่กำหนดและใช้แฟล็กที่เหมาะสมเมื่อรวบรวมแอปพลิเคชัน บนแพลตฟอร์ม Linux หรือโอเพ่นซอร์สนี่ไม่ใช่ปัญหาเนื่องจากคุณสามารถแจกจ่ายซอร์สของคุณและสร้างสคริปต์การติดตั้งที่คอมไพล์โค้ดโดยใช้การเพิ่มประสิทธิภาพที่เหมาะสม บน windows หรือแพลตฟอร์มซอร์สแบบปิดคุณจะต้องแจกจ่ายไฟล์ปฏิบัติการหลาย ๆ ไฟล์โดยแต่ละไฟล์มีการปรับให้เหมาะสม ไบนารีของ windows ที่จะปรับใช้จะขึ้นอยู่กับ CPU ที่ตรวจพบโดยโปรแกรมติดตั้ง msi (โดยใช้การดำเนินการแบบกำหนดเอง)


23
1. Microsoft ไม่เคยอ้างสิทธิ์เหล่านั้นเกี่ยวกับ C # ที่เร็วกว่าการอ้างสิทธิ์คือความเร็วประมาณ 90% พัฒนาได้เร็วขึ้น (และมีเวลาปรับแต่งมากขึ้น) และปราศจากข้อผิดพลาดเนื่องจากหน่วยความจำและความปลอดภัยในการพิมพ์ ซึ่งทั้งหมดนี้เป็นความจริง (ฉันมี C ++ 20 ปีและ 10 ใน C #) 2. ประสิทธิภาพการเริ่มต้นไม่มีความหมายในกรณีส่วนใหญ่ 3. นอกจากนี้ยังได้เร็วขึ้น C # คอมไพเลอร์เช่น LLVM (เพื่อนำออกอินเทลไม่ได้เป็นแอปเปิ้ลแอปเปิ้ล)
เบน

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

8
Microsoft ได้ทำการอ้างสิทธิ์เกี่ยวกับประสิทธิภาพของ. NET ที่เร็วกว่าในเอกสารทางการตลาดก่อนหน้านี้ พวกเขายังเรียกร้องต่างๆเกี่ยวกับคนเก็บขยะว่ามีผลกระทบต่อประสิทธิภาพเพียงเล็กน้อยหรือไม่มีเลย การอ้างสิทธิ์เหล่านี้บางส่วนได้จัดทำเป็นหนังสือหลายเล่ม (บน ASP.NET และ. NET) ในฉบับก่อนหน้านี้ แม้ว่า Microsoft จะไม่ได้บอกเป็นพิเศษว่าแอปพลิเคชัน C # ของคุณจะเร็วกว่าแอปพลิเคชัน C ++ ของคุณ แต่ก็อาจส่งความคิดเห็นและคำขวัญทางการตลาดทั่วไปเช่น "Just-In-Time Means Run-It-Fast" ( msdn.microsoft.com/ en-us / library / ms973894.aspx )
Richard

72
-1 คำพูดพร่ำเพ้อนี้เต็มไปด้วยข้อความที่ไม่ถูกต้องและทำให้เข้าใจผิดเช่นคำที่ชัดเจนว่า "รหัส C # จะไม่เร็วกว่าแอปพลิเคชัน C ++"
BCoates

33
-1. คุณควรอ่านเปอร์โตริโก Mariani VS เรย์มอนด์เฉิน C # VS C ต่อสู้ประสิทธิภาพ: blogs.msdn.com/b/ricom/archive/2005/05/16/418051.aspx กล่าวโดยย่อ: ต้องใช้คนที่ฉลาดที่สุดคนหนึ่งใน Microsoft ในการปรับแต่งให้เหมาะสมเพื่อให้เวอร์ชัน C เร็วกว่ารุ่น C # ธรรมดา
Rolf Bjarne Kvinge

10

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

แก้ไข: ด่าเอาชนะ 9 วิ!


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

7

หากต้องการดูว่าลูปกำลังเพิ่มประสิทธิภาพอยู่หรือไม่ให้ลองเปลี่ยนรหัสของคุณเป็น

root += Math.Sqrt(i);

ans ในทำนองเดียวกันในรหัส C จากนั้นพิมพ์ค่าของรูทนอกลูป


6

บางทีคอมไพเลอร์ c # สังเกตว่าคุณไม่ได้ใช้รูทที่ใดก็ได้ดังนั้นมันจึงข้ามทั้งหมดสำหรับลูป :)

นั่นอาจไม่เป็นเช่นนั้น แต่ฉันสงสัยว่าสาเหตุคืออะไรมันขึ้นอยู่กับการใช้งานคอมไพเลอร์ ลองคอมไพล์โปรแกรม C ของคุณด้วยคอมไพเลอร์ Microsoft (cl.exe ซึ่งมีให้เป็นส่วนหนึ่งของ win32 sdk) ด้วยการเพิ่มประสิทธิภาพและโหมดรีลีส ฉันพนันได้เลยว่าคุณจะเห็นการปรับปรุงที่สมบูรณ์แบบกว่าคอมไพเลอร์อื่น ๆ

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


2
บางทีมันอาจจะรู้ว่า

2
@Neil, @jeff: เห็นด้วยมันสามารถรู้ได้อย่างง่ายดาย การวิเคราะห์แบบคงที่ใน Math.Sqrt () อาจไม่ยากนัก แต่ทั้งนี้ขึ้นอยู่กับการนำไปใช้งาน
John Feminella

5

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

บางทีคุณควรพยายามชนะ เทียบเท่ากับ $ / usr / bin / time my_cprog; / usr / bin / time my_csprog


1
เหตุใดจึงลดลง มีใครคิดว่าการขัดจังหวะและสวิตช์บริบทไม่ส่งผลต่อประสิทธิภาพ? ใครสามารถตั้งสมมติฐานเกี่ยวกับ TLB พลาดการสลับหน้า ฯลฯ ?
ทอม

5

ฉันรวบรวม (ตามรหัสของคุณ) การทดสอบเปรียบเทียบอีกสองรายการใน C และ C # ทั้งสองเขียนอาร์เรย์ขนาดเล็กโดยใช้ตัวดำเนินการโมดูลัสสำหรับการจัดทำดัชนี (มันเพิ่มค่าใช้จ่ายเล็กน้อย แต่เดี๋ยวก่อนเรากำลังพยายามเปรียบเทียบประสิทธิภาพ [ในระดับหยาบ])

รหัส C:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>

void main()
{
    int count = (int)1e8;
    int subcount = 1000;
    double* roots = (double*)malloc(sizeof(double) * subcount);
    clock_t start = clock();
    for (int i = 0 ; i < count; i++)
    {
        roots[i % subcount] = sqrt((double)i);
    }
    clock_t end = clock();
    double length = ((double)end - start) / CLOCKS_PER_SEC;
    printf("Time elapsed: %f\n", length);
}

ใน C #:

using System;

namespace CsPerfTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = (int)1e8;
            int subcount = 1000;
            double[] roots = new double[subcount];
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < count; i++)
            {
                roots[i % subcount] = Math.Sqrt(i);
            }
            TimeSpan runTime = DateTime.Now - startTime;
            Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds / 1000));
        }
    }
}

การทดสอบเหล่านี้เขียนข้อมูลไปยังอาร์เรย์ (ดังนั้นรันไทม์. NET จึงไม่ควรได้รับอนุญาตให้คัดแยก sqrt op) แม้ว่าอาร์เรย์จะมีขนาดเล็กลงมาก (ไม่ต้องการใช้หน่วยความจำมากเกินไป) ฉันรวบรวมสิ่งเหล่านี้ในการกำหนดค่ารุ่นและเรียกใช้จากภายในหน้าต่างคอนโซล (แทนที่จะเริ่มต้นผ่าน VS)

ในคอมพิวเตอร์ของฉันโปรแกรม C # จะแตกต่างกันไประหว่าง 6.2 ถึง 6.9 วินาทีในขณะที่เวอร์ชัน C จะแตกต่างกันไประหว่าง 6.9 ถึง 7.1


5

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

ไม่จำเป็นต้องมีการคาดเดาการศึกษา


ฉันต้องการทราบวิธีการทำ
Josh Stodola

ขึ้นอยู่กับ IDE หรือดีบักเกอร์ของคุณ แตกที่จุดเริ่มต้นของ pgm แสดงหน้าต่างการถอดชิ้นส่วนและเริ่มขั้นตอนเดียว หากใช้ GDB จะมีคำสั่งสำหรับการทำทีละคำสั่ง
Mike Dunlavey

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

FYI: สำหรับฉันสิ่งนี้แสดงให้เห็น VC ++ โดยใช้ fadd และ fsqrt ในขณะที่ C # ใช้ cvtsi2sd และ sqrtsd ซึ่งตามที่ฉันเข้าใจคือคำแนะนำ SSE2 และเร็วกว่ามากเมื่อรองรับ
danio

2

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


ในทางทฤษฎีใช่ ในทางปฏิบัติแทบไม่เคยสร้างความแตกต่างที่วัดได้ หนึ่งหรือสองเปอร์เซ็นต์บางทีถ้าคุณโชคดี
jalf

หรือ - หากคุณมีรหัสบางประเภทที่ใช้ส่วนขยายที่ไม่ได้อยู่ในรายการที่อนุญาตสำหรับตัวประมวลผล "ทั่วไป" สิ่งต่างๆเช่นรสชาติ SSE ลองตั้งค่าเป้าหมายโปรเซสเซอร์ให้สูงขึ้นเพื่อดูว่าคุณได้รับความแตกต่างอะไรบ้าง
gbjbaanb

1

สำหรับฉันแล้วดูเหมือนว่านี่ไม่ได้เกี่ยวข้องกับภาษา แต่จะเกี่ยวข้องกับการใช้ฟังก์ชันรากที่สองที่แตกต่างกัน


ฉันสงสัยอย่างมากว่าการใช้งาน sqrt ที่แตกต่างกันจะทำให้เกิดความเหลื่อมล้ำมาก
Alex Fort

โดยเฉพาะอย่างยิ่งตั้งแต่ใน C # ฟังก์ชันทางคณิตศาสตร์ส่วนใหญ่ยังถือว่ามีความสำคัญต่อประสิทธิภาพและมีการนำไปใช้เช่นนี้
Matthew Olenik

fsqrt เป็นคำสั่งตัวประมวลผล IA-32 ดังนั้นการใช้ภาษาจึงไม่เกี่ยวข้องในทุกวันนี้
ไม่แน่ใจ

ก้าวเข้าสู่ฟังก์ชัน sqrt ของ MSVC ด้วยดีบักเกอร์ มันทำได้มากกว่าการรันคำสั่ง fsqrt
bk1e

1

จริงๆแล้วลูปไม่ได้รับการปรับให้เหมาะสม ฉันรวบรวมรหัสของ John และตรวจสอบ. exe ที่เป็นผลลัพธ์ ความกล้าของลูปมีดังนี้:

 IL_0005:  stloc.0
 IL_0006:  ldc.i4.0
 IL_0007:  stloc.1
 IL_0008:  br.s       IL_0016
 IL_000a:  ldloc.1
 IL_000b:  conv.r8
 IL_000c:  call       float64 [mscorlib]System.Math::Sqrt(float64)
 IL_0011:  pop
 IL_0012:  ldloc.1
 IL_0013:  ldc.i4.1
 IL_0014:  add
 IL_0015:  stloc.1
 IL_0016:  ldloc.1
 IL_0017:  ldc.i4     0x5f5e100
 IL_001c:  ble.s      IL_000a

เว้นแต่รันไทม์จะฉลาดพอที่จะรู้ว่าลูปไม่ทำอะไรเลยและข้ามไป?

แก้ไข: การเปลี่ยน C # เป็น:

 static void Main(string[] args)
 {
      DateTime startTime = DateTime.Now;
      double root = 0.0;
      for (int i = 0; i <= 100000000; i++)
      {
           root += Math.Sqrt(i);
      }
      System.Console.WriteLine(root);
      TimeSpan runTime = DateTime.Now - startTime;
      Console.WriteLine("Time elapsed: " +
          Convert.ToString(runTime.TotalMilliseconds / 1000));
 }

ผลลัพธ์ในเวลาที่ผ่านไป (บนเครื่องของฉัน) จาก 0.047 ถึง 2.17 แต่นั่นเป็นเพียงค่าใช้จ่ายในการเพิ่มตัวดำเนินการเพิ่มเติม 100 ล้านรายหรือไม่?


3
การดู IL ไม่ได้บอกอะไรคุณมากนักเกี่ยวกับการปรับให้เหมาะสมเพราะแม้ว่าคอมไพเลอร์ C # จะทำบางอย่างเช่นการพับและการลบโค้ดที่ตายไปอย่างต่อเนื่อง แต่ IL ก็เข้ารับช่วงต่อและทำส่วนที่เหลือในเวลาโหลด
Daniel Earwicker

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