D เมื่อเทียบกับ C ++ เร็วแค่ไหน?


133

ฉันชอบคุณสมบัติบางอย่างของ D แต่จะสนใจว่ามันมาพร้อมกับการลงโทษรันไทม์หรือไม่?

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

  • D: 18.9 วินาที [ดูด้านล่างสำหรับรันไทม์สุดท้าย]
  • C ++: 3.8 วิ

C ++ เร็วกว่าเกือบห้าเท่าจริง ๆ หรือว่าฉันทำผิดพลาดในโปรแกรม D?

ฉันรวบรวม C ++ ด้วย g ++ -O3 (gcc-snapshot 2011-02-19) และ D พร้อม dmd -O (dmd 2.052) บนเดสก์ท็อป linux ในระดับปานกลาง ผลลัพธ์สามารถทำซ้ำได้ในการวิ่งหลายครั้งและค่าเบี่ยงเบนมาตรฐานเล็กน้อย

ที่นี่โปรแกรม C ++:

#include <iostream>
#include <random>
#include <chrono>
#include <string>

#include <vector>
#include <array>

typedef std::chrono::duration<long, std::ratio<1, 1000>> millisecs;
template <typename _T>
long time_since(std::chrono::time_point<_T>& time) {
      long tm = std::chrono::duration_cast<millisecs>( std::chrono::system_clock::now() - time).count();
  time = std::chrono::system_clock::now();
  return tm;
}

const long N = 20000;
const int size = 10;

typedef int value_type;
typedef long long result_type;
typedef std::vector<value_type> vector_t;
typedef typename vector_t::size_type size_type;

inline value_type scalar_product(const vector_t& x, const vector_t& y) {
  value_type res = 0;
  size_type siz = x.size();
  for (size_type i = 0; i < siz; ++i)
    res += x[i] * y[i];
  return res;
}

int main() {
  auto tm_before = std::chrono::system_clock::now();

  // 1. allocate and fill randomly many short vectors
  vector_t* xs = new vector_t [N];
  for (int i = 0; i < N; ++i) {
    xs[i] = vector_t(size);
      }
  std::cerr << "allocation: " << time_since(tm_before) << " ms" << std::endl;

  std::mt19937 rnd_engine;
  std::uniform_int_distribution<value_type> runif_gen(-1000, 1000);
  for (int i = 0; i < N; ++i)
    for (int j = 0; j < size; ++j)
      xs[i][j] = runif_gen(rnd_engine);
  std::cerr << "random generation: " << time_since(tm_before) << " ms" << std::endl;

  // 2. compute all pairwise scalar products:
  time_since(tm_before);
  result_type avg = 0;
  for (int i = 0; i < N; ++i)
    for (int j = 0; j < N; ++j) 
      avg += scalar_product(xs[i], xs[j]);
  avg = avg / N*N;
  auto time = time_since(tm_before);
  std::cout << "result: " << avg << std::endl;
  std::cout << "time: " << time << " ms" << std::endl;
}

และนี่คือรุ่น D:

import std.stdio;
import std.datetime;
import std.random;

const long N = 20000;
const int size = 10;

alias int value_type;
alias long result_type;
alias value_type[] vector_t;
alias uint size_type;

value_type scalar_product(const ref vector_t x, const ref vector_t y) {
  value_type res = 0;
  size_type siz = x.length;
  for (size_type i = 0; i < siz; ++i)
    res += x[i] * y[i];
  return res;
}

int main() {   
  auto tm_before = Clock.currTime();

  // 1. allocate and fill randomly many short vectors
  vector_t[] xs;
  xs.length = N;
  for (int i = 0; i < N; ++i) {
    xs[i].length = size;
  }
  writefln("allocation: %i ", (Clock.currTime() - tm_before));
  tm_before = Clock.currTime();

  for (int i = 0; i < N; ++i)
    for (int j = 0; j < size; ++j)
      xs[i][j] = uniform(-1000, 1000);
  writefln("random: %i ", (Clock.currTime() - tm_before));
  tm_before = Clock.currTime();

  // 2. compute all pairwise scalar products:
  result_type avg = cast(result_type) 0;
  for (int i = 0; i < N; ++i)
    for (int j = 0; j < N; ++j) 
      avg += scalar_product(xs[i], xs[j]);
  avg = avg / N*N;
  writefln("result: %d", avg);
  auto time = Clock.currTime() - tm_before;
  writefln("scalar products: %i ", time);

  return 0;
}

3
อย่างไรก็ตามโปรแกรมของคุณมีจุดบกพร่องในบรรทัดนี้: avg = avg / N*N(ลำดับการดำเนินการ)
Vladimir Panteleev

4
คุณสามารถลองเขียนโค้ดใหม่โดยใช้การดำเนินการอาร์เรย์ / เวกเตอร์digitalmars.com/d/2.0/arrays.html
Michal Minich

10
เพื่อให้การเปรียบเทียบดีขึ้นคุณควรใช้ back-end ของคอมไพเลอร์เดียวกัน DMD และ DMC ++ หรือ GDC และ G ++
he_the_great

1
@Sion Sheevok น่าเสียดายที่การทำโปรไฟล์ dmd ดูเหมือนจะไม่พร้อมใช้งานสำหรับ Linux? (โปรดแก้ไขฉันถ้าฉันผิด แต่ถ้าฉันบอกว่าdmd ... trace.defฉันได้รับerror: unrecognized file extension def. และเอกสารdmdสำหรับoptlinkพูดถึง Windows เท่านั้น
Lars

1
อ่าไม่เคยสนใจไฟล์. dev ที่มันพ่นออกมา การกำหนดเวลาอยู่ภายในไฟล์. log "มันมีรายการฟังก์ชั่นตามลำดับที่ตัวเชื่อมโยงควรเชื่อมโยง" - นั่นอาจจะช่วย optlink เพื่อเพิ่มประสิทธิภาพบางอย่าง? โปรดทราบว่า "นอกจากนี้ ld สนับสนุนไฟล์" * .def "มาตรฐานอย่างสมบูรณ์ซึ่งอาจระบุไว้ในบรรทัดคำสั่งตัวเชื่อมโยงเช่นไฟล์อ็อบเจ็กต์" ดังนั้นคุณสามารถลองส่ง trace.def ผ่าน -L ได้หากคุณต้องการอย่างยิ่ง ถึง.
Trass3r

คำตอบ:


64

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

-O -inline -release -noboundscheck

แก้ไข : ฉันได้ลองใช้โปรแกรมของคุณกับ g ++, dmd และ gdc แล้ว dmd ล้าหลัง แต่ gdc มีประสิทธิภาพใกล้เคียงกับ g ++ มาก บรรทัดคำสั่งที่ฉันใช้คือgdmd -O -release -inline(gdmd เป็น wrapper รอบ ๆ gdc ซึ่งยอมรับตัวเลือก dmd)

เมื่อดูรายชื่อแอสเซมเบลอร์ดูเหมือนว่าจะไม่มีทั้ง dmd หรือ gdc แบบอินไลน์scalar_productแต่ g ++ / gdc ส่งคำสั่ง MMX ดังนั้นจึงอาจเป็นเวกเตอร์อัตโนมัติสำหรับลูป


3
@CyberShadow: แต่ถ้าคุณลบการตรวจสอบความปลอดภัย ... คุณไม่สูญเสียคุณสมบัติที่สำคัญบางอย่างของ D หรือไม่?
Matthieu M.

33
คุณกำลังสูญเสียคุณสมบัติที่ C ++ ไม่เคยมี ภาษาส่วนใหญ่ไม่ได้ให้คุณเลือก
Vladimir Panteleev

6
@CyberShadow: เราคิดว่านี่เป็นการสร้าง debug vs release หรือไม่?
Francesco

7
@Bernard: ใน -release การตรวจสอบขอบเขตถูกปิดสำหรับโค้ดทั้งหมดยกเว้นฟังก์ชันที่ปลอดภัย ในการปิดการตรวจสอบขอบเขตจริงๆให้ใช้ทั้ง -release และ-noboundscheck
Michal Minich

5
@CyberShadow ขอบคุณ! ด้วยแฟล็กรันไทม์เหล่านี้ปรับปรุงอย่างมาก ตอนนี้ D อยู่ที่ 12.9 วินาที แต่ยังวิ่งได้นานกว่า 3 เท่า. @ Matthieu M. ฉันไม่รังเกียจที่จะทดสอบโปรแกรมด้วยการตรวจสอบขอบเขตในการเคลื่อนไหวช้าและเมื่อมีการดีบักแล้วให้ทำการคำนวณโดยไม่ต้องมีการตรวจสอบขอบเขต (ฉันทำเช่นเดียวกันกับ C ++ ตอนนี้)
ลาร์ส

32

สิ่งสำคัญอย่างหนึ่งที่ทำให้ D ช้าลงคือการดำเนินการจัดเก็บขยะที่ไม่สำคัญ เกณฑ์มาตรฐานที่ไม่เน้นหนัก GC จะแสดงประสิทธิภาพที่คล้ายกันมากกับโค้ด C และ C ++ ที่คอมไพล์ด้วยแบ็กเอนด์ของคอมไพเลอร์เดียวกัน เกณฑ์มาตรฐานที่ทำให้ GC เน้นหนักมากจะแสดงให้เห็นว่า D ทำงานได้อย่างสุดยอด อย่างไรก็ตามโปรดมั่นใจได้ว่านี่เป็นปัญหาด้านคุณภาพของการนำไปใช้งานเดียว (แม้ว่าจะรุนแรง) ไม่ใช่การรับประกันความช้า นอกจากนี้ D ยังช่วยให้คุณสามารถเลือกที่จะไม่ใช้ GC และปรับแต่งการจัดการหน่วยความจำในบิตที่มีความสำคัญต่อประสิทธิภาพในขณะที่ยังคงใช้ในโค้ดของคุณที่มีประสิทธิภาพน้อยกว่า 95%

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


1
ฉันสังเกตเห็นการเปลี่ยนแปลงอย่างหนึ่งของคุณคือการเปลี่ยนแปลงจากการหารเป็นการกะบิต นั่นควรเป็นสิ่งที่คอมไพเลอร์ทำไม่ใช่หรือ?
GManNickG

3
@GMan: ใช่ถ้าทราบค่าที่คุณหารด้วยเวลาคอมไพล์ ไม่ถ้าทราบค่าที่รันไทม์เท่านั้นซึ่งเป็นกรณีที่ฉันทำการเพิ่มประสิทธิภาพนั้น
dsimcha

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

13
@GMan: การเปลี่ยนบิตจะใช้ได้ผลก็ต่อเมื่อจำนวนที่คุณหารด้วยกำลังสอง คอมไพเลอร์ไม่สามารถพิสูจน์สิ่งนี้ได้หากทราบหมายเลขที่รันไทม์เท่านั้นและการทดสอบและการแยกย่อยจะช้ากว่าการใช้คำสั่ง div กรณีของฉันผิดปกติเพราะรู้ค่าที่รันไทม์เท่านั้น แต่ฉันรู้เวลาคอมไพล์ว่ามันจะมีกำลังสอง
dsimcha

7
โปรดทราบว่าโปรแกรมที่โพสต์ในตัวอย่างนี้ไม่ได้ทำการจัดสรรในส่วนที่ใช้เวลานาน
Vladimir Panteleev

27

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

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


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

11
ในฐานะวิศวกรที่ผ่าฟัน C ++ คุณคือฮีโร่ของฉัน อย่างไรก็ตามนี่ควรเป็นความคิดเห็นไม่ใช่คำตอบ
Alan

14

ดูเหมือนว่าจะเป็นปัญหาด้านคุณภาพของการนำไปใช้อย่างแน่นอน

ฉันทำการทดสอบด้วยรหัสของ OP และทำการเปลี่ยนแปลงบางอย่าง จริงๆแล้วฉันทำให้ D ทำงานได้เร็วขึ้นสำหรับ LDC / clang ++ โดยทำงานบนสมมติฐานที่ว่าอาร์เรย์ต้องได้รับการจัดสรรแบบไดนามิก ( xsและสเกลาร์ที่เกี่ยวข้อง) ดูตัวเลขด้านล่าง

คำถามสำหรับ OP

เป็นความตั้งใจที่จะใช้เมล็ดพันธุ์เดียวกันสำหรับการวนซ้ำของ C ++ แต่ละครั้งในขณะที่ไม่ใช่สำหรับ D

ติดตั้ง

ฉันได้ปรับแต่งแหล่งที่มา D ดั้งเดิม (ขนานนามscalar.d) เพื่อให้พกพาระหว่างแพลตฟอร์ม สิ่งนี้เกี่ยวข้องกับการเปลี่ยนประเภทของตัวเลขที่ใช้ในการเข้าถึงและแก้ไขขนาดของอาร์เรย์เท่านั้น

หลังจากนี้ฉันได้ทำการเปลี่ยนแปลงต่อไปนี้:

  • ใช้uninitializedArrayเพื่อหลีกเลี่ยงค่าเริ่มต้นสำหรับสเกลาร์ใน xs (อาจสร้างความแตกต่างได้มากที่สุด) สิ่งนี้มีความสำคัญเนื่องจาก D โดยปกติแล้วค่าเริ่มต้นจะเปิดทุกอย่างแบบเงียบซึ่ง C ++ ไม่ทำ

  • แยกรหัสการพิมพ์ออกและแทนที่writeflnด้วยwriteln

  • เปลี่ยนการนำเข้าเป็นแบบเลือก
  • ใช้ตัวดำเนินการ pow ( ^^) แทนการคูณด้วยตนเองสำหรับขั้นตอนสุดท้ายของการคำนวณค่าเฉลี่ย
  • ลบsize_typeและแทนที่อย่างเหมาะสมด้วยindex_typeนามแฝงใหม่

... จึงส่งผลให้scalar2.cpp( pastebin ):

    import std.stdio : writeln;
    import std.datetime : Clock, Duration;
    import std.array : uninitializedArray;
    import std.random : uniform;

    alias result_type = long;
    alias value_type = int;
    alias vector_t = value_type[];
    alias index_type = typeof(vector_t.init.length);// Make index integrals portable - Linux is ulong, Win8.1 is uint

    immutable long N = 20000;
    immutable int size = 10;

    // Replaced for loops with appropriate foreach versions
    value_type scalar_product(in ref vector_t x, in ref vector_t y) { // "in" is the same as "const" here
      value_type res = 0;
      for(index_type i = 0; i < size; ++i)
        res += x[i] * y[i];
      return res;
    }

    int main() {
      auto tm_before = Clock.currTime;
      auto countElapsed(in string taskName) { // Factor out printing code
        writeln(taskName, ": ", Clock.currTime - tm_before);
        tm_before = Clock.currTime;
      }

      // 1. allocate and fill randomly many short vectors
      vector_t[] xs = uninitializedArray!(vector_t[])(N);// Avoid default inits of inner arrays
      for(index_type i = 0; i < N; ++i)
        xs[i] = uninitializedArray!(vector_t)(size);// Avoid more default inits of values
      countElapsed("allocation");

      for(index_type i = 0; i < N; ++i)
        for(index_type j = 0; j < size; ++j)
          xs[i][j] = uniform(-1000, 1000);
      countElapsed("random");

      // 2. compute all pairwise scalar products:
      result_type avg = 0;
      for(index_type i = 0; i < N; ++i)
        for(index_type j = 0; j < N; ++j)
          avg += scalar_product(xs[i], xs[j]);
      avg /= N ^^ 2;// Replace manual multiplication with pow operator
      writeln("result: ", avg);
      countElapsed("scalar products");

      return 0;
    }

หลังจากการทดสอบscalar2.d(ซึ่งจัดลำดับความสำคัญของการปรับให้เหมาะสมที่สุดสำหรับความเร็ว) ด้วยความอยากรู้อยากเห็นฉันแทนที่ลูปmainด้วยสิ่งที่foreachเทียบเท่าและเรียกมันว่าscalar3.d( pastebin ):

    import std.stdio : writeln;
    import std.datetime : Clock, Duration;
    import std.array : uninitializedArray;
    import std.random : uniform;

    alias result_type = long;
    alias value_type = int;
    alias vector_t = value_type[];
    alias index_type = typeof(vector_t.init.length);// Make index integrals portable - Linux is ulong, Win8.1 is uint

    immutable long N = 20000;
    immutable int size = 10;

    // Replaced for loops with appropriate foreach versions
    value_type scalar_product(in ref vector_t x, in ref vector_t y) { // "in" is the same as "const" here
      value_type res = 0;
      for(index_type i = 0; i < size; ++i)
        res += x[i] * y[i];
      return res;
    }

    int main() {
      auto tm_before = Clock.currTime;
      auto countElapsed(in string taskName) { // Factor out printing code
        writeln(taskName, ": ", Clock.currTime - tm_before);
        tm_before = Clock.currTime;
      }

      // 1. allocate and fill randomly many short vectors
      vector_t[] xs = uninitializedArray!(vector_t[])(N);// Avoid default inits of inner arrays
      foreach(ref x; xs)
        x = uninitializedArray!(vector_t)(size);// Avoid more default inits of values
      countElapsed("allocation");

      foreach(ref x; xs)
        foreach(ref val; x)
          val = uniform(-1000, 1000);
      countElapsed("random");

      // 2. compute all pairwise scalar products:
      result_type avg = 0;
      foreach(const ref x; xs)
        foreach(const ref y; xs)
          avg += scalar_product(x, y);
      avg /= N ^^ 2;// Replace manual multiplication with pow operator
      writeln("result: ", avg);
      countElapsed("scalar products");

      return 0;
    }

ฉันรวบรวมการทดสอบแต่ละอย่างโดยใช้คอมไพเลอร์ที่ใช้ LLVM เนื่องจาก LDC ดูเหมือนจะเป็นตัวเลือกที่ดีที่สุดสำหรับการคอมไพล์ D ในแง่ของประสิทธิภาพ ในการติดตั้ง x86_64 Arch Linux ของฉันฉันใช้แพ็คเกจต่อไปนี้:

  • clang 3.6.0-3
  • ldc 1:0.15.1-4
  • dtools 2.067.0-2

ฉันใช้คำสั่งต่อไปนี้เพื่อรวบรวมแต่ละคำสั่ง:

  • C ++: clang++ scalar.cpp -o"scalar.cpp.exe" -std=c++11 -O3
  • D: rdmd --compiler=ldc2 -O3 -boundscheck=off <sourcefile>

ผล

ผลลัพธ์ ( ภาพหน้าจอของเอาต์พุตคอนโซลดิบ ) ของแต่ละเวอร์ชันของซอร์สดังนี้:

  1. scalar.cpp (C ++ ดั้งเดิม):

    allocation: 2 ms
    
    random generation: 12 ms
    
    result: 29248300000
    
    time: 2582 ms
    

    C ++ กำหนดมาตรฐานที่2,582 มิลลิวินาที

  2. scalar.d (แก้ไขแหล่งที่มา OP):

    allocation: 5 ms, 293 μs, and 5 hnsecs 
    
    random: 10 ms, 866 μs, and 4 hnsecs 
    
    result: 53237080000
    
    scalar products: 2 secs, 956 ms, 513 μs, and 7 hnsecs 
    

    วิ่งไปหานี้~ 2,957 มิลลิวินาที ช้ากว่าการใช้งาน C ++ แต่ไม่มากเกินไป

  3. scalar2.d (การเปลี่ยนแปลงประเภทดัชนี / ความยาวและการเพิ่มประสิทธิภาพ uninitializedArray):

    allocation: 2 ms, 464 μs, and 2 hnsecs
    
    random: 5 ms, 792 μs, and 6 hnsecs
    
    result: 59
    
    scalar products: 1 sec, 859 ms, 942 μs, and 9 hnsecs
    

    ในคำอื่น ๆ~ 1,860 มิลลิวินาที จนถึงตอนนี้อยู่ในขั้นนำ

  4. scalar3.d (foreaches):

    allocation: 2 ms, 911 μs, and 3 hnsecs
    
    random: 7 ms, 567 μs, and 8 hnsecs
    
    result: 189
    
    scalar products: 2 secs, 182 ms, and 366 μs
    

    ~ 2182 msช้ากว่าscalar2.dแต่เร็วกว่ารุ่น C ++

สรุป

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


8

dmd เป็นการใช้งานอ้างอิงของภาษาดังนั้นงานส่วนใหญ่จึงถูกใส่ไว้ในส่วนหน้าเพื่อแก้ไขข้อบกพร่องแทนที่จะเพิ่มประสิทธิภาพแบ็กเอนด์

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

โดยทั่วไปแล้วเวกเตอร์จะถูกนำไปใช้กับโครงสร้างที่ const ref เหมาะสมที่สุด ดูsmallptDเทียบกับsmallptสำหรับตัวอย่างในโลกแห่งความจริงที่มีการดำเนินการเวกเตอร์และการสุ่มจำนวนมาก

โปรดทราบว่า 64-Bit สามารถสร้างความแตกต่างได้เช่นกัน ครั้งหนึ่งฉันพลาดสิ่งนั้นใน x64 gcc รวบรวมรหัส 64 บิตในขณะที่ dmd ยังคงค่าเริ่มต้นเป็น 32 (จะเปลี่ยนเมื่อ codegen 64 บิตครบกำหนด) มีการเร่งความเร็วที่โดดเด่นด้วย "dmd -m64 ... "


7

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

แต่มีอยู่เพียงไม่กี่กรณีที่ D ยืนเป็นโอกาสที่ดีของการตี C ++ สำหรับความเร็ว สิ่งสำคัญที่ควรคำนึงถึงคือการประมวลผลสตริง ด้วยความสามารถในการแบ่งส่วนอาร์เรย์ของ D ทำให้สตริง (และอาร์เรย์โดยทั่วไป) สามารถประมวลผลได้เร็วกว่าที่คุณสามารถทำได้ใน C ++ สำหรับ D1 ตัวประมวลผล XML ของ Tango นั้นเร็วมากโดยหลัก ๆ แล้วความสามารถในการแบ่งอาร์เรย์ของ D (และหวังว่า D2 จะมีตัวแยกวิเคราะห์ XML ที่รวดเร็วในทำนองเดียวกันเมื่อตัวประมวลผลที่กำลังทำงานกับ Phobos เสร็จสิ้นแล้ว) ดังนั้นท้ายที่สุดแล้วการที่ D หรือ C ++ จะเร็วขึ้นนั้นขึ้นอยู่กับสิ่งที่คุณกำลังทำอยู่

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

ท้ายที่สุดแล้วสิ่งที่สำคัญคือ dmd ทำงานได้ดีเพียงใดสำหรับแอปพลิเคชันเฉพาะของคุณ แต่ฉันยอมรับว่าคงจะดีอย่างแน่นอนที่ทราบว่า C ++ และ D โดยทั่วไปเปรียบเทียบได้ดีเพียงใด ตามทฤษฎีแล้วควรจะเหมือนกันมาก แต่ขึ้นอยู่กับการนำไปใช้งานจริงๆ ฉันคิดว่าชุดของเกณฑ์มาตรฐานที่ครอบคลุมจะต้องใช้เพื่อทดสอบว่าทั้งสองเปรียบเทียบกันอย่างไรในปัจจุบัน


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

1
ทำได้ง่าย (เร็วกว่า) กว่า C ++ iostreams แต่นั่นเป็นปัญหาหลักในการใช้งานไลบรารี (ในเวอร์ชันที่รู้จักทั้งหมดจากผู้ให้บริการยอดนิยม)
Ben Voigt

4

คุณสามารถเขียนโค้ด C คือ D ได้เท่าที่เร็วกว่านั้นจะขึ้นอยู่กับหลาย ๆ อย่าง:

  • คุณใช้คอมไพเลอร์อะไร
  • คุณใช้ฟีเจอร์อะไร
  • คุณเพิ่มประสิทธิภาพอย่างจริงจังเพียงใด

ความแตกต่างในข้อแรกไม่ยุติธรรมที่จะลากเข้าข้อที่สองอาจทำให้ C ++ ได้เปรียบเนื่องจากมีคุณสมบัติที่หนักน้อยกว่า ประการที่สามคือความสนุก: โค้ด D ในบางวิธีนั้นง่ายกว่าในการปรับให้เหมาะสมเพราะโดยทั่วไปแล้วจะเข้าใจง่ายกว่า นอกจากนี้ยังมีความสามารถในการเขียนโปรแกรมเชิงกำเนิดในระดับสูงทำให้สามารถเขียนสิ่งต่างๆเช่น verbose และซ้ำซาก แต่รวดเร็วในรูปแบบที่สั้นกว่า


3

ดูเหมือนปัญหาด้านคุณภาพของการนำไปใช้งาน ตัวอย่างเช่นนี่คือสิ่งที่ฉันได้ทำการทดสอบ:

import std.datetime, std.stdio, std.random;

version = ManualInline;

immutable N = 20000;
immutable Size = 10;

alias int value_type;
alias long result_type;
alias value_type[] vector_type;

result_type scalar_product(in vector_type x, in vector_type y)
in
{
    assert(x.length == y.length);
}
body
{
    result_type result = 0;

    foreach(i; 0 .. x.length)
        result += x[i] * y[i];

    return result;
}

void main()
{   
    auto startTime = Clock.currTime();

    // 1. allocate vectors
    vector_type[] vectors = new vector_type[N];
    foreach(ref vec; vectors)
        vec = new value_type[Size];

    auto time = Clock.currTime() - startTime;
    writefln("allocation: %s ", time);
    startTime = Clock.currTime();

    // 2. randomize vectors
    foreach(ref vec; vectors)
        foreach(ref e; vec)
            e = uniform(-1000, 1000);

    time = Clock.currTime() - startTime;
    writefln("random: %s ", time);
    startTime = Clock.currTime();

    // 3. compute all pairwise scalar products
    result_type avg = 0;

    foreach(vecA; vectors)
        foreach(vecB; vectors)
        {
            version(ManualInline)
            {
                result_type result = 0;

                foreach(i; 0 .. vecA.length)
                    result += vecA[i] * vecB[i];

                avg += result;
            }
            else
            {
                avg += scalar_product(vecA, vecB);
            }
        }

    avg = avg / (N * N);

    time = Clock.currTime() - startTime;
    writefln("scalar products: %s ", time);
    writefln("result: %s", avg);
}

เมื่อManualInlineกำหนดไว้ฉันจะได้ 28 วินาที แต่ถ้าไม่มีฉันได้ 32 ดังนั้นคอมไพเลอร์จึงไม่ได้รวมฟังก์ชันง่ายๆนี้ไว้ด้วยซ้ำซึ่งฉันคิดว่ามันควรจะเป็นอย่างชัดเจน

(บรรทัดคำสั่งของฉันคือdmd -O -noboundscheck -inline -release ...)


1
การกำหนดเวลาของคุณไม่มีความหมายเว้นแต่คุณจะเปรียบเทียบกับการกำหนดเวลา C ++ ของคุณด้วย
deceleratedcaviar

3
@ แดเนียล: คุณไม่มีประเด็น มันเป็นการแสดงให้เห็นถึงการเพิ่มประสิทธิภาพ D แบบแยกส่วนกล่าวคือสำหรับข้อสรุปที่ฉันระบุไว้: "ดังนั้นคอมไพเลอร์จึงไม่ได้รวมฟังก์ชันง่ายๆนี้ไว้ด้วยซ้ำซึ่งฉันคิดว่ามันชัดเจนว่าควรจะเป็น" ฉันพยายามเปรียบเทียบกับ C ++ ด้วยซ้ำตามที่ระบุไว้อย่างชัดเจนในประโยคแรก : "ดูเหมือนว่าจะเป็นปัญหาด้านคุณภาพของการนำไปใช้งาน"
GManNickG

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