ฉันควรใช้ String.format () ของ Java หรือไม่หากประสิทธิภาพมีความสำคัญ


215

เราต้องสร้าง Strings ตลอดเวลาเพื่อบันทึกผลลัพธ์และอื่น ๆ ในเวอร์ชั่น JDK เราได้เรียนรู้ว่าเมื่อไรควรใช้StringBuffer(ผนวกหลายอัน, เธรดที่ปลอดภัย) และStringBuilder(ผนวกท้ายหลายอัน, ไม่ใช้เธรดที่ปลอดภัย)

คำแนะนำในการใช้String.format()คืออะไร? มันมีประสิทธิภาพหรือไม่หรือเราถูกบังคับให้ติดกับการต่อข้อมูลสำหรับหนึ่ง liners ที่ประสิทธิภาพเป็นสิ่งสำคัญหรือไม่

เช่นแบบเก่าที่น่าเกลียด

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";

เทียบกับรูปแบบใหม่ที่เป็นระเบียบ (String.format ซึ่งอาจช้ากว่า)

String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);

หมายเหตุ: กรณีการใช้งานเฉพาะของฉันคือสตริงการบันทึก 'หนึ่งซับ' ตลอดทั้งรหัสของฉัน พวกมันไม่เกี่ยวข้องกับลูปดังนั้นStringBuilderมันจึงมีน้ำหนักมากเกินไป ฉันสนใจเป็นString.format()พิเศษ


28
ทำไมคุณไม่ลองทดสอบดูล่ะ?
Ed S.

1
หากคุณกำลังสร้างผลงานนี้ฉันคิดว่ามันต้องอ่านได้โดยมนุษย์ในฐานะที่มนุษย์สามารถอ่านได้ ให้พูดมากที่สุด 10 บรรทัดต่อวินาที ฉันคิดว่าคุณจะพบว่ามันไม่สำคัญว่าคุณจะใช้วิธีการแบบใดหากมันช้าลงอย่างเห็นได้ชัดผู้ใช้อาจชื่นชมมัน ;) ไม่เลย StringBuilder ไม่ได้เป็นเฮฟวี่เวทในสถานการณ์ส่วนใหญ่
Peter Lawrey

9
@ ปีเตอร์ไม่มันไม่เหมาะกับการอ่านแบบเรียลไทม์โดยมนุษย์! มีไว้เพื่อช่วยวิเคราะห์เมื่อสิ่งผิดปกติ โดยทั่วไปแล้วเอาต์พุตของบันทึกจะเป็นหลายพันบรรทัดต่อวินาทีดังนั้นจึงต้องมีประสิทธิภาพ
อากาศ

5
หากคุณผลิตหลายพันบรรทัดต่อวินาทีฉันขอแนะนำ 1) ใช้ข้อความที่สั้นกว่าแม้จะไม่มีข้อความเช่น CSV ธรรมดาหรือไบนารี 2) อย่าใช้ String เลยคุณสามารถเขียนข้อมูลลงใน ByteBuffer โดยไม่ต้องสร้าง วัตถุใด ๆ (เป็นข้อความหรือไบนารี) 3) พื้นหลังการเขียนข้อมูลลงดิสก์หรือซ็อกเก็ต คุณควรจะสามารถรักษาประมาณ 1 ล้านบรรทัดต่อวินาที (โดยพื้นฐานเท่าที่ระบบย่อยดิสก์ของคุณจะอนุญาต) คุณสามารถบรรลุผลการระเบิดได้ถึง 10 เท่า
Peter Lawrey

7
สิ่งนี้ไม่เกี่ยวข้องกับกรณีทั่วไป แต่สำหรับการเข้าสู่ระบบโดยเฉพาะอย่างยิ่ง LogBack (เขียนโดยผู้เขียน Log4j ดั้งเดิม) มีรูปแบบของการบันทึกพารามิเตอร์ที่จัดการปัญหาที่แน่นอนนี้ - logback.qos.ch/manual/architecture.html#ParametrizedLogging
Matt Passell

คำตอบ:


123

ฉันเขียนชั้นเรียนขนาดเล็กเพื่อทดสอบซึ่งมีประสิทธิภาพที่ดีขึ้นของทั้งสองและ + มาก่อนรูปแบบ คูณ 5 ถึง 6 ลองด้วยตัวคุณเอง

import java.io.*;
import java.util.Date;

public class StringTest{

    public static void main( String[] args ){
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;

    for( i = 0; i< 100000; i++){
        String s = "Blah" + i + "Blah";
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<100000; i++){
        String s = String.format("Blah %d Blah", i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    }
}

การทำงานด้านบนสำหรับ N ที่แตกต่างกันแสดงให้เห็นว่าทั้งสองทำงานเป็นเส้นตรง แต่String.formatช้ากว่า 5-30 เท่า

เหตุผลก็คือในการใช้งานในปัจจุบันString.formatจะแยกวิเคราะห์อินพุตด้วยนิพจน์ทั่วไปจากนั้นเติมพารามิเตอร์ การต่อข้อมูลกับ plus จะได้รับการปรับปรุงโดย javac (ไม่ใช่โดย JIT) และใช้StringBuilder.appendโดยตรง

การเปรียบเทียบรันไทม์


12
มีข้อบกพร่องหนึ่งข้อในการทดสอบนี้ซึ่งไม่ได้แสดงถึงการจัดรูปแบบสตริงทั้งหมดอย่างสมบูรณ์ บ่อยครั้งที่มีเหตุผลเกี่ยวข้องในสิ่งที่จะรวมและตรรกะในการจัดรูปแบบค่าเฉพาะลงในสตริง การทดสอบจริงใด ๆ ควรดูที่สถานการณ์จริง
Orion Adrian

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

25
มันดูคล้ายกับ microbenchmark ที่จะได้รับการปรับปรุงให้ดีขึ้นในลักษณะที่ไม่ได้ผลมาก
David H. Clements

20
อีกมาตรฐานไมโคร - ดำเนินการไม่ดี วิธีการทั้งสองวิธีปรับขนาดตามคำสั่งของขนาด วิธีการเกี่ยวกับการใช้งาน 100, 1000, 10000, 1000000, การดำเนินงาน หากคุณรันการทดสอบเพียงครั้งเดียวตามลำดับความสำคัญในแอปพลิเคชันที่ไม่ได้ทำงานบนแกนที่แยกได้ ไม่มีทางที่จะบอกว่าความแตกต่างสามารถถูกเขียนออกมาเป็น 'ผลข้างเคียง' เนื่องจากการสลับบริบทกระบวนการพื้นหลังและอื่น ๆ
Evan Plaice

8
ยิ่งกว่านั้นเมื่อคุณไม่ได้ออกจาก JIT หลักก็ไม่สามารถเตะได้
Jan Zyka

241

ฉันใช้รหัสhhafezและเพิ่มการทดสอบหน่วยความจำ :

private static void test() {
    Runtime runtime = Runtime.getRuntime();
    long memory;
    ...
    memory = runtime.freeMemory();
    // for loop code
    memory = memory-runtime.freeMemory();

ฉันเรียกใช้สิ่งนี้แยกกันสำหรับแต่ละวิธีตัวดำเนินการ '+', String.format และ StringBuilder (การเรียก toString ()) ดังนั้นหน่วยความจำที่ใช้จะไม่ได้รับผลกระทบจากวิธีการอื่น ฉันเพิ่มการเรียงต่อกันมากขึ้นทำให้สตริงเป็น "Blah" + i + "Blah" + i + "Blah" + i + "Blah"

ผลลัพธ์มีดังนี้ (เฉลี่ย 5 การรันแต่ละครั้ง):
Approach Time (ms) หน่วยความจำที่จัดสรร (ยาว)
ตัวดำเนินการ '+' 747 320,504
String.format 16484 373,312
StringBuilder 769 57,344

เราจะเห็นว่า String '+' และ StringBuilder เหมือนกันกับเวลาจริง ๆ แต่ StringBuilder นั้นมีประสิทธิภาพมากขึ้นในการใช้หน่วยความจำ นี่เป็นสิ่งสำคัญมากเมื่อเรามีการเรียกใช้บันทึกจำนวนมาก (หรือคำสั่งอื่น ๆ ที่เกี่ยวข้องกับสตริง) ในช่วงเวลาสั้น ๆ เพียงพอดังนั้น Garbage Collector จะไม่ทำความสะอาดอินสแตนซ์สตริงจำนวนมากซึ่งเป็นผลมาจากตัวดำเนินการ '+'

และโน้ต BTW อย่าลืมตรวจสอบระดับการบันทึกก่อนสร้างข้อความ

สรุป:

  1. ฉันจะใช้ StringBuilder ต่อไป
  2. ฉันมีเวลามากเกินไปหรือมีชีวิตน้อยเกินไป

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

39
ไม่ถูกต้อง ขออภัยที่จะทื่อ แต่จำนวน upvotes มันดึงดูดไม่มีอะไรน่าตกใจ การใช้ตัว+ดำเนินการคอมไพล์StringBuilderรหัสเทียบเท่า Microbenchmarks เช่นนี้ไม่ใช่วิธีการวัดประสิทธิภาพที่ดี - ทำไมไม่ใช้ jvisualvm มันอยู่ใน jdk ด้วยเหตุผล String.format() จะช้าลง แต่เนื่องจากเวลาในการแยกสตริงรูปแบบแทนการจัดสรรวัตถุใด ๆ ชะลอการเข้าสู่ระบบการสร้างสิ่งประดิษฐ์จนกว่าคุณจะแน่ใจว่าพวกเขากำลังจำเป็นต้องเป็นคำแนะนำที่ดี แต่ถ้ามันจะมีผลกระทบต่อประสิทธิภาพการทำงานจะอยู่ในสถานที่ที่ไม่ถูกต้อง
CurtainDog

1
@CurtainDog ความคิดเห็นของคุณถูกโพสต์ในสี่ปีคุณสามารถชี้ไปที่เอกสารประกอบหรือสร้างคำตอบแยกต่างหากเพื่อแก้ไขความแตกต่างได้หรือไม่?
kurtzbot

1
อ้างอิงในการสนับสนุนการแสดงความคิดเห็น @ CurtainDog ของ: stackoverflow.com/a/1532499/2872712 นั่นคือ + เป็นที่ต้องการยกเว้นว่ามันจะทำในวง
apricot

And a note, BTW, don't forget to check the logging level before constructing the message.ไม่ใช่คำแนะนำที่ดี สมมติว่าเรากำลังพูดถึงjava.util.logging.*โดยเฉพาะการตรวจสอบระดับการบันทึกคือเมื่อคุณกำลังพูดถึงการประมวลผลขั้นสูงที่จะทำให้เกิดผลเสียต่อโปรแกรมที่คุณไม่ต้องการเมื่อโปรแกรมไม่ได้เปิดการบันทึกในระดับที่เหมาะสม การจัดรูปแบบสตริงไม่ใช่การประมวลผลประเภทนั้นทั้งหมด การจัดรูปแบบเป็นส่วนหนึ่งของjava.util.loggingเฟรมเวิร์กและตัวบันทึกเองตรวจสอบระดับการบันทึกก่อนที่ฟอร์แมตเตอร์จะถูกเรียกใช้
searchengine27

30

มาตรฐานทั้งหมดที่นำเสนอในที่นี้มีข้อบกพร่องบางอย่างดังนั้นผลลัพธ์จึงไม่น่าเชื่อถือ

ฉันประหลาดใจที่ไม่มีใครใช้JMHในการเปรียบเทียบดังนั้นฉันจึงทำได้

ผล:

Benchmark             Mode  Cnt     Score     Error  Units
MyBenchmark.testOld  thrpt   20  9645.834 ± 238.165  ops/s  // using +
MyBenchmark.testNew  thrpt   20   429.898 ±  10.551  ops/s  // using String.format

หน่วยคือการดำเนินการต่อวินาทียิ่งมากยิ่งดี รหัสที่มามาตรฐานรหัสที่มาเกณฑ์มาตรฐานมีการใช้ OpenJDK IcedTea 2.5.4 Java Virtual Machine

ดังนั้นรูปแบบเก่า (ใช้ +) จึงเร็วกว่ามาก


5
การตีความนี้จะง่ายกว่ามากหากคุณใส่หมายเหตุซึ่งเป็น "+" และ "รูปแบบ"
AjahnCharles

21

รูปแบบที่น่าเกลียดแบบเก่าของคุณนั้นรวบรวมโดย JAVAC 1.6 โดยอัตโนมัติดังนี้:

StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s =  sb.toString();

ดังนั้นจึงไม่มีความแตกต่างระหว่างสิ่งนี้กับการใช้งาน StringBuilder

String.format เป็นเฮฟวี่เวทมากขึ้นเพราะมันสร้างฟอร์แมตเตอร์ใหม่แยกวิเคราะห์สตริงรูปแบบการป้อนข้อมูลของคุณสร้าง StringBuilder ผนวกทุกอย่างเข้ากับมันแล้วเรียก toString ()


ในแง่ของความสามารถในการอ่านรหัสที่คุณโพสต์นั้นยุ่งยากกว่า String.format ("คุณจะได้อะไรถ้าคุณคูณ% d ด้วย% d?", varSix, varNine);
dusktreader

12
ไม่มีความแตกต่างระหว่าง+และStringBuilderแน่นอน น่าเสียดายที่มีข้อมูลผิด ๆ มากมายในคำตอบอื่น ๆ ในกระทู้นี้ how should I not be measuring performanceฉันเกือบจะอยากที่จะเปลี่ยนคำถามเพื่อ
CurtainDog

12

String.format ของ Java ทำงานได้ดังนี้:

  1. มันแยกวิเคราะห์สตริงรูปแบบการขยายเข้าไปในรายการของรูปแบบชิ้น
  2. มันวนซ้ำรูปแบบการแสดงผลใน StringBuilder ซึ่งเป็นอาร์เรย์ที่ปรับขนาดตัวเองตามความจำเป็นโดยการคัดลอกลงในอาร์เรย์ใหม่ สิ่งนี้จำเป็นเพราะเรายังไม่รู้ว่าจะจัดสรรสตริงสุดท้ายได้มากเพียงใด
  3. StringBuilder.toString () คัดลอกบัฟเฟอร์ภายในของเขาลงใน String ใหม่

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

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

ฉันคาดการณ์ว่าเครื่องมือเพิ่มประสิทธิภาพจะปรับการประมวลผลสตริงรูปแบบให้เหมาะสม ถ้าเป็นเช่นนั้นคุณเหลือเทียบเท่าตัดจำหน่ายประสิทธิภาพการทำงานด้วยตนเอง unrolling String.format ของคุณลงใน StringBuilder


5
ฉันไม่คิดว่าการเก็งกำไรของคุณเกี่ยวกับการเพิ่มประสิทธิภาพของการประมวลผลสตริงรูปแบบถูกต้อง ในบางการทดสอบที่แท้จริงของโลกโดยใช้ Java 7 ผมพบว่าการใช้String.formatในลูปภายใน (วิ่งล้านครั้ง) ส่งผลให้เกินกว่า 10% java.util.Formatter.parse(String)ของเวลาการดำเนินการของฉันใช้เวลาในการ สิ่งนี้ดูเหมือนจะบ่งชี้ว่าในลูปภายในคุณควรหลีกเลี่ยงการโทรFormatter.formatหรืออะไรก็ตามที่เรียกมันรวมถึงPrintStream.format(ข้อบกพร่องใน lib มาตรฐาน IMO ของ Java โดยเฉพาะอย่างยิ่งเนื่องจากคุณไม่สามารถแคชสตริงรูปแบบการแยกวิเคราะห์)
Andy MacKinlay

8

หากต้องการขยาย / แก้ไขคำตอบแรกข้างต้นไม่ใช่การแปลที่ String.format จะช่วยได้จริง
สิ่งที่ String.format จะช่วยคือเมื่อคุณพิมพ์วันที่ / เวลา (หรือรูปแบบตัวเลข ฯลฯ ) ซึ่งมีความแตกต่างในการแปล (l10n) (เช่นบางประเทศจะพิมพ์ 04Feb2009 และอื่น ๆ จะพิมพ์ Feb042009)
ด้วยการแปลคุณเพียงแค่พูดถึงการย้ายสตริงที่สามารถแก้ไขได้ภายนอกใด ๆ (เช่นข้อความแสดงข้อผิดพลาดและอะไรก็ตาม) ลงในบันเดิลคุณสมบัติเพื่อให้คุณสามารถใช้บันเดิลที่ถูกต้องสำหรับภาษาที่เหมาะสมโดยใช้ ResourceBundle และ MessageFormat

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


1
ฉันจะบอกว่าการทำงานที่ชาญฉลาด String.format กับการเรียงต่อกันแบบธรรมดานั้นมาลงในสิ่งที่คุณต้องการฉันคิดว่ามันไม่ถูกต้อง ประสิทธิภาพที่ชาญฉลาดการต่อข้อมูลจะดีกว่ามาก สำหรับรายละเอียดเพิ่มเติมโปรดดูคำตอบของฉัน
Adam Stelmaszczyk

6

ในตัวอย่างของคุณประสิทธิภาพในการประมวลผลไม่แตกต่างกันมากนัก แต่มีปัญหาอื่น ๆ ที่ควรพิจารณา: คือการกระจายตัวของหน่วยความจำ แม้ว่าการดำเนินการเรียงต่อกันกำลังสร้างสตริงใหม่แม้ว่ามันจะเป็นการชั่วคราว (มันต้องใช้เวลาในการ GC และมันก็ทำงานได้มากกว่า) String.format () สามารถอ่านได้มากขึ้นและเกี่ยวข้องกับการกระจายตัวน้อย

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

นอกจากนี้สิ่งอื่นที่คุณควรระวัง: ระวังการใช้ซับสตริง () ตัวอย่างเช่น:

String getSmallString() {
  String largeString = // load from file; say 2M in size
  return largeString.substring(100, 300);
}

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

  return new String(largeString.substring(100, 300));

หรือ

  return String.format("%s", largeString.substring(100, 300));

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


8
มีค่าชี้ให้เห็น "คำถามที่เกี่ยวข้อง" เป็นจริง C # และดังนั้นจึงไม่สามารถใช้ได้
อากาศ

เครื่องมือใดที่คุณใช้ในการวัดการแตกแฟรกเมนต์ของหน่วยความจำและการแตกแฟรกเมนต์ยังสร้างความแตกต่างความเร็วให้กับ ram
kritzikratzi

เป็นค่าที่ชี้ให้เห็นว่าวิธีการ substring ถูกเปลี่ยนจาก Java 7 + ตอนนี้ควรส่งคืนการแสดงสตริงใหม่ที่มีเฉพาะอักขระสตริงย่อยเท่านั้น นั่นหมายความว่าไม่จำเป็นต้องโทรกลับสตริง :: ใหม่
João Rebelo

5

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

ตอนนี้ถ้าคุณไม่เคยวางแผนที่จะแปลอะไรแล้วทั้งพึ่งพา Java สร้างขึ้นในแปลง + StringBuilderผู้ประกอบการเข้าสู่ หรือใช้ของจาวาStringBuilderอย่างชัดเจน


3

มุมมองอื่นจากการบันทึกมุมมองเท่านั้น

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

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

คุณไม่จำเป็นต้องเชื่อมต่อ / ฟอร์แมตเว้นแต่คุณต้องการเข้าสู่ระบบ ให้บอกว่าถ้าฉันกำหนดวิธีการเช่นนี้

public void logDebug(String... args, Throwable t) {
    if(debugOn) {
       // call concat methods for all args
       //log the final debug message
    }
}

ในวิธีนี้ cancat / formatter จะไม่ถูกเรียกจริง ๆ เลยถ้าข้อความ debug และ debugOn = false

แม้ว่ามันจะยังดีกว่าที่จะใช้ StringBuilder แทนการจัดรูปแบบที่นี่ แรงจูงใจหลักคือการหลีกเลี่ยงสิ่งเหล่านี้

ในเวลาเดียวกันฉันไม่ต้องการเพิ่มบล็อก "if" สำหรับแต่ละคำสั่งการบันทึกตั้งแต่

  • มันส่งผลกระทบต่อการอ่าน
  • ลดความครอบคลุมในการทดสอบหน่วยของฉัน - นั่นคือความสับสนเมื่อคุณต้องการให้แน่ใจว่าทุกบรรทัดมีการทดสอบ

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


คุณสามารถใช้ประโยชน์จากห้องสมุดที่มีอยู่เช่น slf4j-api ซึ่งอ้างว่าจะจัดการกับ usecase นี้ด้วยคุณสมบัติการบันทึกพารามิเตอร์ slf4j.org/faq.html#logging_performance
ammianus

2

ฉันเพิ่งแก้ไขการทดสอบ hhafez เพื่อรวม StringBuilder StringBuilder เร็วกว่า String.format 33 เท่าโดยใช้ไคลเอ็นต์ jdk 1.6.0_10 บน XP การใช้สวิตช์ -server จะลดปัจจัยให้เหลือ 20

public class StringTest {

   public static void main( String[] args ) {
      test();
      test();
   }

   private static void test() {
      int i = 0;
      long prev_time = System.currentTimeMillis();
      long time;

      for ( i = 0; i < 1000000; i++ ) {
         String s = "Blah" + i + "Blah";
      }
      time = System.currentTimeMillis() - prev_time;

      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         String s = String.format("Blah %d Blah", i);
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         new StringBuilder("Blah").append(i).append("Blah");
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);
   }
}

ในขณะที่สิ่งนี้อาจฟังดูรุนแรง แต่ฉันคิดว่ามันมีความเกี่ยวข้องเฉพาะในกรณีที่หายากเพราะตัวเลขสัมบูรณ์นั้นค่อนข้างต่ำ: 4 วินาทีสำหรับการโทร String.format แบบง่าย ๆ 1 ล้านครั้งก็ถือว่าใช้ได้ - ตราบใดที่ฉันใช้มันเพื่อเข้าสู่ระบบหรือ ชอบ.

ปรับปรุง:เป็นแหลมออกโดย sjbotha ในความคิดเห็นที่ทดสอบ StringBuilder .toString()ไม่ถูกต้องเพราะมันจะหายไปเป็นครั้งสุดท้าย

ปัจจัยที่มีความเร็วสูงขึ้นถูกต้องจากString.format(.)ไปStringBuilderคือ 23 ในเครื่องของฉัน (16 กับ-serverสวิทช์)


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

ฉันทำอย่างนั้นการ for loop ใช้เวลา 0 ms แต่ถึงแม้ว่ามันต้องใช้เวลามันก็แค่เพิ่มปัจจัย
the.duckman

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

1

นี่คือรุ่นที่แก้ไขของรายการ hhafez มันมีตัวเลือกสร้างสตริง

public class BLA
{
public static final String BLAH = "Blah ";
public static final String BLAH2 = " Blah";
public static final String BLAH3 = "Blah %d Blah";


public static void main(String[] args) {
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;
    int numLoops = 1000000;

    for( i = 0; i< numLoops; i++){
        String s = BLAH + i + BLAH2;
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        String s = String.format(BLAH3, i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        StringBuilder sb = new StringBuilder();
        sb.append(BLAH);
        sb.append(i);
        sb.append(BLAH2);
        String s = sb.toString();
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

}

}

เวลาหลังจากสำหรับวง 391 เวลาหลังจากสำหรับวง 4163 เวลาหลังจากสำหรับวง 227


0

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

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


1
bytecode สำหรับตัวอย่างที่สองเรียก String.format อย่างแน่นอนแต่ฉันจะตกใจถ้ามีการต่อข้อมูลแบบง่าย ทำไมคอมไพเลอร์จะใช้สตริงรูปแบบซึ่งจะต้องมีการแยกวิเคราะห์?
Jon Skeet

ฉันใช้ "bytecode" ซึ่งฉันควรจะพูดว่า "binary code" เมื่อมันมาถึง jmps และ movs มันอาจเป็นโค้ดเดียวกันทั้งหมด
ใช่ - เจคนั่น

0

พิจารณาใช้"hello".concat( "world!" )สำหรับสตริงจำนวนน้อยในการต่อข้อมูล มันอาจจะดีกว่าสำหรับประสิทธิภาพมากกว่าวิธีอื่น ๆ

หากคุณมีมากกว่า 3 สตริงลองพิจารณาใช้ StringBuilder หรือเพียงแค่สตริงขึ้นอยู่กับคอมไพเลอร์ที่คุณใช้

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