programing

Java의 String을 사용해야 합니까?퍼포먼스가 중요한 경우 format().

newsource 2022. 8. 7. 16:56

Java의 String을 사용해야 합니까?퍼포먼스가 중요한 경우 format().

현악기에서는 JDK를 사용하는 .StringBuffer 추가, 세이프) 및 ( 추가, 스레드 세이프)StringBuilder(미국의)

어떻게 하면 좋을까요?String.format()효율이 좋은가요, 아니면 퍼포먼스가 중요한 한 줄짜리 연결 방식을 고집해야 하나요?

예: 못생긴 오래된 스타일,

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

vs. 깔끔한 새로운 스타일(String).포맷이 느릴 수 있습니다).

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

참고: 코드 전체에 수백 개의 '원라이너' 로그 문자열이 있는 것이 구체적인 사용 사례입니다.에, 「 」는 「 」StringBuilder너무 무거워요.에에 i에 관심이 있다String.format()체적적구

hafez 코드를 가져와 메모리 테스트를 추가했습니다.

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

각 접근 방식, '+' 연산자 String에 대해 별도로 실행합니다.사용되는 메모리는 다른 접근법에 영향을 받지 않도록 포맷 및 String Builder(String()를 호출).나는 더 많은 연결을 추가하여 문자열을 "Blah" + "Blah" + "Blah" + "Blah" + "i" + "Blah" 로 만들었습니다.

결과는 다음과 같습니다(각각 평균 5회 주행).
시간(long는 memory allocated ( long ) 。
320 '+' 연 747747 320,504
' 373' 16484 373,312
Builder 57,344 String Builder 769 57,344

String '+'와 String Builder는 시간적으로 거의 동일하지만 String Builder가 메모리 사용 효율이 훨씬 높습니다.가비지 콜렉터가 '+' 연산자로 인해 발생하는 많은 문자열 인스턴스를 정리할 수 없도록 짧은 시간 내에 많은 로그 콜(또는 문자열과 관련된 다른 문)이 있는 경우 이는 매우 중요합니다.

그리고 주의사항, BTW는 메시지를 작성하기 전에 로깅 수준을 확인하는 것을 잊지 마십시오.

결론:

  1. String Builder를 계속 사용합니다.
  2. 나는 시간이 너무 많거나 너무 적다.

둘 중 어느 쪽이 더 성능이 좋고 +가 형식보다 먼저인지 테스트하기 위해 작은 클래스를 작성했습니다.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);

    }
}

를 다른 하면 둘 다 선형으로 알 수 있지만, 둘 다 선형으로 동작합니다.String.format5에서 30까지입니다.

「 」에서는, 「 」가 되고 있기 입니다.String.format는 먼저 정규식으로 입력을 해석한 후 파라미터를 입력합니다.JIT가에되어 javac(J)을 사용합니다.IT부문(IT부문)은StringBuilder.append직접적으로.

런타임 비교

여기에 제시된 벤치마크는 모두 일부 결함이 있기 때문에 결과를 신뢰할 수 없습니다.

벤치마킹에 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 IceTea 2.5.4 Java Virtual Machine이 사용되었습니다.

구식(+)이 훨씬 빠릅니다.

오래된 못생긴 스타일은 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();

따라서 String Builder를 사용하는 것과 전혀 차이가 없습니다.

String.format은 새로운 Formatter를 만들고 입력 형식 문자열을 해석하며 String Builder를 만들고 모든 것을 추가하여 String()을 호출하기 때문에 훨씬 더 무겁습니다.

자바 문자열포맷은 다음과 같이 동작합니다.

  1. 포맷 스트링을 해석하여 포맷 청크 목록으로 분할합니다.
  2. 형식 청크를 반복하여 String Builder로 렌더링합니다.String Builder는 기본적으로 필요에 따라 크기를 조정하는 어레이입니다.최종 String을 할당하는 크기를 아직 모르기 때문에 이것이 필요합니다.
  3. String Builder.toString()은 내부 버퍼를 새 문자열에 복사합니다.

이 데이터의 최종 수신처가 스트림(예: 웹 페이지 렌더링 또는 파일 쓰기)인 경우 스트림에 포맷 청크를 직접 조립할 수 있습니다.

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

옵티마이저는 포맷 문자열 처리를 최적화하여 제거할 것으로 추측합니다.이 경우 String을 수동으로 언롤할 수 있는 동등한 상각 퍼포먼스가 남습니다.String Builder로 포맷합니다.

는 String 도움포맷이 도움이 될 것 같아요
(가 있는 4일 및 2009년 4일 인쇄하는 형식은 현지화(l10n)의 차이가 있는 날짜/시간(또는 숫자 형식 등)을 인쇄하는 경우에 도움이 됩니다(즉, 일부 국가에서는 2009년 2월 4일을 인쇄하고 다른 국가에서는 2009년 2월 4일을 인쇄합니다).
번역에서는 ResourceBundle 및 MessageFormat을 사용하여 올바른 언어에 적합한 번들을 사용할 수 있도록 외부로 사용할 수 있는 문자열(에러 메시지 등)을 속성 번들로 이동하는 것을 말합니다.

「String」의 「format」은 대로 .포맷과 플레인 연결은 결국 원하는 대로 됩니다.연결보다 .format에 대한 호출을 보고 싶다면 반드시 그렇게 하십시오.
결국, 코드는 쓰여진 것보다 훨씬 더 많이 읽힌다.

이 예에서는 퍼포먼스 프로발비도 크게 다르지 않지만 메모리 플래그멘테이션 등 고려해야 할 다른 문제가 있습니다.연결 작업에서도 임시 문자열이라도 새 문자열이 생성됩니다(GC에 시간이 걸리고 더 많은 작업이 필요함).스트링format()은 읽기 쉽고 플래그멘테이션이 적습니다.

또한 특정 형식을 많이 사용하는 경우 Formatter() 클래스를 직접 사용할 수 있습니다(모든 String).format()은 One Use Formatter 인스턴스를 인스턴스화합니다).

또, 서브스트링()의 사용에 주의해 주세요.예를 들어 다음과 같습니다.

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));

두 번째 양식은 다른 작업을 동시에 수행할 때 더 유용할 수 있습니다.

일반적으로 String을 사용해야 합니다.포맷은 비교적 빠르고 글로벌화를 지원하므로(사용자가 읽을 수 있는 내용을 실제로 쓰려고 한다고 가정합니다).또한 문장당 3개 이상의 문자열을 번역하려는 경우(특히 문법 구조가 크게 다른 언어의 경우)에 비해 하나의 문자열을 번역하려는 경우 글로벌화하기가 더 쉬워집니다.

번역할 계획이 전혀 없다면 Java의 기본 제공 + 연산자 변환에 의존하십시오.StringBuilder또는 Java를 사용합니다.StringBuilder명쾌하게

Logging 관점에서만 볼 수 있는 다른 관점.

이 스레드에 대한 로그와 관련된 많은 논의가 있기 때문에 답변에 대한 경험을 추가하려고 합니다.누군가 유용하다고 생각할지도 몰라

포메터를 사용한 로그의 동기는 스트링 연결을 피하는 것이 원인이라고 생각합니다.기본적으로 스트링을 기록하지 않을 경우 string concat의 오버헤드가 발생하지 않도록 합니다.

로그를 작성하지 않는 한 실제로 콘센트/포맷할 필요가 없습니다.예를 들어 다음과 같은 방법을 정의하면

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

이 접근 방식에서는 cancat/formatter가 디버깅 메시지이고 debugOn = false이면 실제로 호출되지 않습니다.

그러나 여기서는 포맷터 대신 String Builder를 사용하는 것이 좋습니다.주된 동기는 그 어떤 것도 피하는 것이다.

동시에 로그 스테이트먼트마다 "if" 블록을 추가하는 것은 좋아하지 않습니다.

  • 가독성에 영향을 주다
  • 유닛 테스트의 커버리지를 줄입니다.모든 회선을 테스트하고 싶은 경우는 혼란스럽습니다.

따라서 위와 같은 방법으로 로깅 유틸리티 클래스를 만들어 성능 저하나 이와 관련된 다른 문제에 대해 걱정하지 않고 어디에서나 사용하는 것을 선호합니다.

방금 Hafez의 테스트를 String Builder를 포함하도록 수정했습니다.String Builder는 String보다 33배 빠릅니다.포맷은 XP에서 jdk 1.6.0_10 클라이언트를 사용하여 수행합니다.-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);
   }
}

극단적으로 들릴 수 있지만, 절대 수치가 100만 개의 단순한 문자열에 4초라는 매우 낮은 경우이기 때문에 극히 드문 경우에만 관련이 있다고 생각합니다.포맷 콜은 로그 등에 사용하는 한 괜찮습니다.

업데이트: 댓글에서 sjbotha가 지적한 바와 같이 String Builder 테스트는 최종 테스트가 없으므로 유효하지 않습니다..toString()

「」의 올바른 업 .String.format(.)로로 합니다.StringBuilder, , 16은 16개입니다(16개입니다.-server스위치)를 클릭합니다.

다음은 hafez 엔트리의 변경된 버전입니다.여기에는 문자열 작성기 옵션이 포함되어 있습니다.

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의 경과시간

이에 대한 답은 특정 Java 컴파일러가 생성하는 바이트 코드를 어떻게 최적화하느냐에 따라 크게 달라집니다.문자열은 불변하며 이론적으로 각 "+" 연산은 새로운 연산을 생성할 수 있습니다.그러나 컴파일러는 긴 문자열을 작성하는 중간 단계를 최적화합니다.위의 두 줄의 코드가 완전히 동일한 바이트 코드를 생성할 수 있습니다.

현재 환경에서 코드를 반복적으로 테스트하는 것이 유일한 진정한 방법입니다.문자열을 양방향으로 반복적으로 연결하는 QD 앱을 작성하여 서로에 대해 타임아웃이 어떻게 되는지 확인합니다.

을 검토해 주세요."hello".concat( "world!" )★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★다른 접근 방식보다 성능이 더 좋을 수 있습니다.

문자열이 3개 이상인 경우 사용하는 컴파일러에 따라 String Builder 또는 String만 사용하는 것을 검토하십시오.

언급URL : https://stackoverflow.com/questions/513600/should-i-use-javas-string-format-if-performance-is-important