//Java
public class Main {
protected static int global;
public static void main(String[] args) {
long t1 = System.nanoTime();
int value = 0;
for (int i = 0; i < 100 * 1000 *1000; i++) {
value = calculate(value);
}
System.out.println(value);
long t2 = System.nanoTime();
System.out.println("Execution time: " + ((t2 - t1) * 1e-6) + " milliseconds");
}
protected static int calculate(int arg) {
//L1: assert (arg >= 0) : "should be positive";
//L2: if (arg < 0) throw new IllegalArgumentException("arg = " + arg + " < 0");
global = arg * 6;
global += 3;
global /= 2;
return arg + 2;
}
}
javac Main.java
java Main
Execution time: 16.550917 milliseconds
//C
int global;
clock_t t1, t2;
int main(){
t1 = clock();
int value = 0;
for (int i = 0; i < 100 * 1000 *1000; i++) {
value = calculate(value);
}
printf("%d \n",value);
t2 = clock();
float diff = (((float)t2 - (float)t1) / 1000000.0F ) * 1000;
printf("Execution time: %f milliseconds \n",diff);
return 0;
}
int calculate(int arg){
global = arg * 6;
global += 3;
global /= 2;
return arg + 2;
}
gcc main.c -std=c99 -o main
./main
Execution time: 703.835022 milliseconds
ทำไมทำ C ถึงทำเวลาได้แย่กว่า Java เมื่อจำนวนลูปเพิ่มขึ้น แต่เมื่อจำนวนลูปน้อยๆ C กลับทำเวลาได้ดีกว่า
ลองใส่ inline ข้างหน้าฟังชั่น calculate ดูคับ
ผมไม่ทราบนะว่าทำไม อันนี้เป็นผลลัพท์จากเครื่องผมครับ
Java : Sun Java 1.7.0_51
C : Clang 3.5.0 (MSYS2)
GCC 4.9.2 ก็ทำได้พอ ๆ กันนะครับ
เครื่องที่ใช้เป็น
Edit: Java ผมลองอีกที ทำได้ประมาณ 34 ms ครับ
Edit 2: โค๊ดจับเวลาของคุณในภาษา C มีปัญหาครับ คือ ผมเดาว่าคุณใช้ Linux คุณก็เลยหารด้วย 100000 แต่บน Windows เนี่ยมันแค่ 1000 tick ต่อวินาทีเองครับ (ดังนั้นต้องหารพัน) ผมเข้าใจว่าคุณจับเวลาได้ถูกแล้วล่ะ บน C ช้ากว่าจริง ๆ
อ่า ลองใส่ -O3 (เปิด optimization level 3) ในคำสั่ง gcc ดูนะครับ
ที่ผมลองดูมันเร็วขึ้น 10 เท่าเลย
อ่ารบกวนหน่อยครับ พอจะทราบข้อเสียของการใช้ -O3 หน่อยได้หรือเปล่าครับ หรือว่าไม่มีครับ เห็นมีระดับให้เลือกใส่ได้ด้วย สงสัยว่ามีกี่ระดับถ้าเอาระดับสูงๆจะเกิดอะไรขึ้นครับ ขอบคุณครับ
ผมเจออะไรเด็ดๆ มาด้วยหล่ะ 3.10 Options That Control Optimization
แต่อยากถามผู้ที่ลองนำไปแล้วแล้วหน่ะครับว่าผลเป็นรูปธรรมยังไงบ้าง
เท่าที่ผมทราบนะครับ เวลาเปิด Optimization ผลลัพท์ที่ได้อาจจะไม่ตรงกับโค๊ดที่เราเขียนน่ะครับ แต่ผลลัพท์ควรจะตรงกัน
เวลาเปิด asm ดูอาจจะงงว่า เฮ้ย เราไม่ได้เขียนแบบนี้นี่หว่า
ทั้งนี้ถ้าเป็น optimization ระดับสูงมาก ๆ อาจจะเจอว่าผลลัพท์ไม่ตรงตามที่คาดไว้ด้วยครับ
ข้อเสียของการใช้
-O3
คือ space-time tradeoff ครับ ซึ่งมีหลายตัวอย่างมากๆ แต่ผมรู้จักไม่กี่อัน เช่น เทคนิคที่เรียกว่า loop unrolling กล่าวคือถ้าคอมไพล์ลูปง่ายๆ อย่างลูปที่จำนวนรอบเป็นค่าคงที่ ตัวคอมไพลเลอร์อาจจะพยายามกำจัด loop ทิ้งไป ดังนี้พอคอมไพล์แล้วอาจกลายเป็น
จะเห็นว่าแค่นี้ก็ประหยัดเวลาการตรวจเงื่อนไขของลูป
for
ไปได้ครึ่งๆ เลยทีเดียว ส่วนการคอมไพล์จริงอาจจะแตกออกมาจนไม่ต้องตรวจเงื่อนไขเลยก็ได้ครับในคู่มือของ gcc (เว็บที่แปะมานั่นแหละ) เค้าบอกเลยเลยว่า
-O1
เปิด flag optimize ตัวไหนบ้าง-O2
เปิดตัวไหนบ้าง ถ้าอยากรู้โค้ดเบื้องหลังจริงๆ แนะนำให้คอมไพล์เทียบกันเป็น assembly มาดูเลยครับ โดยอาจจะเลือกเปิดทีละ flag ที่อยากรู้ก็ได้ส่วนเรื่องเปิด
-O3
แล้วโปรแกรมที่ได้ออกมาดันบั๊กจนหลายๆ คนแนะนำว่าเปิดแค่-O2
พอ มันเป็นเรื่องเมื่อกว่าสิบปีมาแล้วครับ ตอนนี้สบายใจได้ ไปดูว่าเวลาที่ใช้ในการคอมไพล์/ขนาดผลลัพธ์ที่ได้จากการคอมไพล์มันใหญ่เกินรับได้หรือเปล่าดีกว่าผมทดลองปรับลูปให้เหลือแค่ for (int i = 0; i < 100 * 1000 ; i++) ใน C จะทำงานได้เร็วกว่า แต่เมื่อเพิ่มจำนวนลูป for (int i = 0; i < 100 * 1000 * 1000 ; i++) C กลับทำได้แย่กว่าเสียอย่างนั้น ตอนแรกคิดว่าที่ช้าลงน่าจะเกิดจากการเรียกใช้งาน ฟังชั่น calculate แต่พอลองคอมเมนต์ฟังก์ชั่นในลูป ก็ยังช้าเหมือนเดิมแสดงว่าที่ช้าลงน่าเกิดจำนวนลูปที่เพิ่มขึ้น (มันช้าลงนะถูก แต่ช้ากว่า Java นี่ซิ) ผมพยามที่แปลงโค๊ด Java บางส่วนของผมมาเป็น native เลยหาโด๊ดทดสอบ แต่โด๊ดนี้เมื่อแปลงเป็น native ดันช้ากว่าเสียอย่างนั้น เดียวจะลองใส่ -O3 ดูว่าจะเป็นอย่างไรขอบคุณครับ
โอ...เท่าที่ผมลองดู
ใส่ -O3 กับไม่ใส่เนี่ย..ความเร็วมันต่างกันฟ้ากับเหวเลยนะครับเนี่ย
อันนี้ไม่ใส่ -O3
ถ้าใส่ -O3
ส่วนบน Java
C ผมใช้ Clang LLVM 3.5
ส่วน Java ผมใช้ Java 1.8.0
Com ผม
CPU: i5 2.3 GHz
RAM: 8GB
OS X 10.10.1
C/C++ ถ้าไม่เปิด optimize อะไรเลย มันจะแปลงโค้ดตรงๆ ไม่เปลี่ยนอะไรเลยครับ (มีคูณอยู่ในเงื่อนไข for มันก็คูณให้ทุกรอบ) เพื่อให้ดีบักง่ายครับ เวลาคอมไพล์จริงๆ ต้อง -O3 หรือ -Ofast ทุกครั้งครับ
อั๊ยยะ ผมนี่เรียกเพื่อนมาดูเลย -O3 มันดีอย่างนี้นี่เอง
ปกติชีวิตก็วนเวียนอยู่กับ Java ไม่ค่อยมีประการณ์กับ C/C++เท่าไร ขอบคุณทุกคำตอบครับ
แต่เอ่อ gcc มันตกกระป๋องแล้วเหรอครับ ทุกท่านใช้ clang กันหมดเลย 555
Clang อ่าน Error/Warning ง่ายกว่าครับ ไม่ต้องตกใจไป :)
ช้ากว่าเพราะเป็นการรัน optimized java เทียบกับ unoptimized C ครับ
เพราะโปรแกรมมันรันนานพอให้ jit ของจาวาทำงาน
โดยทั่วไปถ้าจะทำ benchmark แบบนี้ ต้องเปิด optimization ประมาณ -O2 ครับ
ถ้าจะรันจาวาแบบไม่ jit สามารถลองได้โดย
java -Djava.compiler=NONE Bench
เอิ่มพอสั่งปิด นี่มันเข้าโหมด interpreter เลยนะครับ ช้าลงสามร้อยห้าสิบเท่าเห็นจะได้ (จากต่ำสุดที่ 26 ms ปาไปไม่ต่ำกว่า 9100 ms)
Oracle Server JRE 1.8.0_20-b26 บน Windows 7 64-bit Intel Core i5 (i5-3320M) @2.6GHz แรม 16 GB
ใช่ครับ ดูเหมือนจะไม่มีวิธีปิดเฉพาะ optimization
กรณี Java JIT ทำงานคงเรียกว่า optimization ไม่ได้นะครับ เพราะมันเป็นค่า default และ JIT เองก็เป็น character ของ Java อยู่แล้ว
กรณีนี้ควรเรียกว่ารูปแบบของคำสั่งเหมาะกับ JIT มากกว่าครับ
Russia is just nazi who accuse the others for being nazi.
someone once said : ผมก็ด่าของผมอยู่นะ :)