String Builder를 루프에서 재사용하는 것이 좋습니까?
스트링 빌더 긴 있습니다.StringBuilder
을 사용하다
for (loop condition) {
StringBuilder sb = new StringBuilder();
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
「 」를 인스턴스화하고 .StringBuilder
은은해??해 ???그리고 대신 다음과 같이 삭제를 호출하는 것이 더 나을까요?
StringBuilder sb = new StringBuilder();
for (loop condition) {
sb.delete(0, sb.length);
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
두 번째는 미니벤치마크가 25% 더 빠릅니다.
public class ScratchPad {
static String a;
public static void main( String[] args ) throws Exception {
long time = System.currentTimeMillis();
for( int i = 0; i < 10000000; i++ ) {
StringBuilder sb = new StringBuilder();
sb.append( "someString" );
sb.append( "someString2"+i );
sb.append( "someStrin4g"+i );
sb.append( "someStr5ing"+i );
sb.append( "someSt7ring"+i );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
time = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10000000; i++ ) {
sb.delete( 0, sb.length() );
sb.append( "someString" );
sb.append( "someString2"+i );
sb.append( "someStrin4g"+i );
sb.append( "someStr5ing"+i );
sb.append( "someSt7ring"+i );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
}
}
결과:
25265
17969
이것은 JRE 1.6.0_07에 관한 것입니다.
편집한 Jon Sket의 아이디어를 바탕으로 버전 2를 소개합니다.결과는 똑같아요.
public class ScratchPad {
static String a;
public static void main( String[] args ) throws Exception {
long time = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10000000; i++ ) {
sb.delete( 0, sb.length() );
sb.append( "someString" );
sb.append( "someString2" );
sb.append( "someStrin4g" );
sb.append( "someStr5ing" );
sb.append( "someSt7ring" );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
time = System.currentTimeMillis();
for( int i = 0; i < 10000000; i++ ) {
StringBuilder sb2 = new StringBuilder();
sb2.append( "someString" );
sb2.append( "someString2" );
sb2.append( "someStrin4g" );
sb2.append( "someStr5ing" );
sb2.append( "someSt7ring" );
a = sb2.toString();
}
System.out.println( System.currentTimeMillis()-time );
}
}
결과:
5016
7516
더 빠른 속도:
public class ScratchPad {
private static String a;
public static void main( String[] args ) throws Exception {
final long time = System.currentTimeMillis();
// Pre-allocate enough space to store all appended strings.
// StringBuilder, ultimately, uses an array of characters.
final StringBuilder sb = new StringBuilder( 128 );
for( int i = 0; i < 10000000; i++ ) {
// Resetting the string is faster than creating a new object.
// Since this is a critical loop, every instruction counts.
sb.setLength( 0 );
sb.append( "someString" );
sb.append( "someString2" );
sb.append( "someStrin4g" );
sb.append( "someStr5ing" );
sb.append( "someSt7ring" );
setA( sb.toString() );
}
System.out.println( System.currentTimeMillis() - time );
}
private static void setA( final String aString ) {
a = aString;
}
}
솔리드 코드를 기술하는 철학에서 메서드의 내부 작업은 클라이언트 객체로부터 숨겨집니다.을 해도 .StringBuilder
을 사용하다루프 외부에서 선언하는 것이 더 빠르고 코드를 크게 복잡하게 만들지 않으므로 개체를 다시 사용합니다.
훨씬 더 복잡하고 오브젝트 인스턴스화가 병목 현상임을 확실히 알고 있더라도 코멘트해 주십시오.
세 번의 실행과 다음 답변이 있습니다.
$ java ScratchPad
1567
$ java ScratchPad
1569
$ java ScratchPad
1570
세 번의 주행과 다른 답:
$ java ScratchPad2
1663
2231
$ java ScratchPad2
1656
2233
$ java ScratchPad2
1658
2242
를StringBuilder
의 초기 버퍼 사이즈는 메모리 재설정을 방지하기 위해 약간의 퍼포먼스가 향상됩니다.
솔리드 코드 작성 철학에서는 String Builder를 루프 안에 넣는 것이 항상 좋습니다.이렇게 하면 의도한 코드를 벗어날 수 없습니다.
다음으로 String Builder의 가장 큰 특징은 루프가 실행되는 동안 더 커지는 것을 방지하기 위해 초기 크기를 제공하는 것입니다.
for (loop condition) {
StringBuilder sb = new StringBuilder(4096);
}
좋아, 이제 무슨 일인지 이해했고 말이 되네
는 는라 i i i i i i .toString
을 char[]
복사본을 받지 않은 문자열 생성자로 변환합니다.그 후, 다음의 「쓰기」조작시에 카피가 작성됩니다(예:delete
)의 경우는 이랬다고 생각합니다.StringBuffer
(비밀을 줄이다)안 돼요. - 안 돼요.toString
색인 및 합니다.String
복사본을 가져오는 컨스트럭터.
' the ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★.StringBuilder
버퍼 내의 동일한 문자 배열을 사용하여 문자열당 1개의 데이터 복사본을 만듭니다. 것을 내고 있다StringBuilder
새로운 기본 버퍼를 생성할 때마다 새로운 스트링을 작성할 때 해당 버퍼가 복사됩니다(특정 경우에는 무의미하게 복사되지만 안전상의 이유로 복사됩니다).
이 모든 것이 두 번째 버전의 효율화로 이어지지만 동시에 더 추악한 코드라고 말할 수 있습니다.
Sun Java 컴파일러에 내장된 최적화로 인해 StringBuilder(StringBuffers pre-J2SE 5.0)가 String 연결되면 자동으로 생성되므로 질문의 첫 번째 예는 다음과 같습니다.
for (loop condition) {
String s = "some string";
. . .
s += anotherString;
. . .
passToMethod(s);
}
IMO는 어느 쪽이 더 읽기 쉬울까요?최적화를 시도하면 일부 플랫폼에서는 이점을 얻을 수 있지만 다른 플랫폼에서는 손해를 볼 수 있습니다.
그러나 성능 문제가 실제로 발생할 경우 최적화를 통해 문제를 해결할 수 있습니다.먼저 String Builder의 버퍼 크기를 Jon Sket별로 명시적으로 지정합니다.
현대 JVM은 이런 것들에 대해 매우 스마트합니다.나는 그것을 재추측하지 않고 유지보수가 용이하지 않은 해킹적인 것을 할 것이다.성능 향상을 검증하는 생산 데이터로 적절한 벤치 마크를 하지 않는 한(및 문서화)
Windows에서의 소프트웨어 개발 경험에 비추어 볼 때, 루프 중에 String Builder를 클리어하는 것이 String Builder를 반복할 때마다 인스턴스화하는 것보다 더 나은 성능을 발휘합니다.이 메모리를 클리어하면 추가 할당 없이 해당 메모리를 즉시 덮어쓸 수 있습니다.Java 가비지 컬렉터에 대해서는 잘 모릅니다만, (다음 문자열이 String Builder를 성장시키지 않는 한) 해방과 재할당이 인스턴스화보다 유리하다고 생각합니다.
(내 의견은 다른 사람들이 제안하는 것과 반대된다.흠. 벤치마킹할 시간입니다.)
setLength 또는 delete를 실행하면 성능이 향상되는 이유는 대부분 버퍼의 올바른 크기를 학습하고 메모리 할당을 덜 하기 때문입니다.일반적으로 문자열 최적화를 컴파일러에 맡길 것을 권장합니다.단, 퍼포먼스가 중요한 경우에는 버퍼의 예상 크기를 미리 계산해 두는 경우가 많습니다.기본 String Builder 크기는 16자입니다.그 이상으로 확장하면 크기를 조정해야 합니다.사이징은 퍼포먼스가 저하되는 부분입니다.다음은 이를 보여주는 또 다른 미니 벤치마크입니다.
private void clear() throws Exception {
long time = System.currentTimeMillis();
int maxLength = 0;
StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10000000; i++ ) {
// Resetting the string is faster than creating a new object.
// Since this is a critical loop, every instruction counts.
//
sb.setLength( 0 );
sb.append( "someString" );
sb.append( "someString2" ).append( i );
sb.append( "someStrin4g" ).append( i );
sb.append( "someStr5ing" ).append( i );
sb.append( "someSt7ring" ).append( i );
maxLength = Math.max(maxLength, sb.toString().length());
}
System.out.println(maxLength);
System.out.println("Clear buffer: " + (System.currentTimeMillis()-time) );
}
private void preAllocate() throws Exception {
long time = System.currentTimeMillis();
int maxLength = 0;
for( int i = 0; i < 10000000; i++ ) {
StringBuilder sb = new StringBuilder(82);
sb.append( "someString" );
sb.append( "someString2" ).append( i );
sb.append( "someStrin4g" ).append( i );
sb.append( "someStr5ing" ).append( i );
sb.append( "someSt7ring" ).append( i );
maxLength = Math.max(maxLength, sb.toString().length());
}
System.out.println(maxLength);
System.out.println("Pre allocate: " + (System.currentTimeMillis()-time) );
}
public void testBoth() throws Exception {
for(int i = 0; i < 5; i++) {
clear();
preAllocate();
}
}
그 결과 오브젝트 재사용이 예상 크기의 버퍼 작성보다 약 10% 빠른 것으로 나타났습니다.
LOL, String Builder에서 스트링을 조합하여 성능을 비교한 것은 처음입니다.그 때문에, 「+」를 사용하면, 한층 더 고속;D가 됩니다.String Builder를 사용하여 "locality" 개념으로 문자열 전체를 빠르게 검색할 수 있습니다.
빈번한 변경이 필요 없는 String 값을 자주 취득하는 시나리오에서는 Stringbuilder를 사용하면 문자열 취득 성능이 향상됩니다.이것이 Stringbuilder를 사용하는 목적입니다.그것의 핵심 목적을 MIS-Test하지 마십시오.
어떤 사람들은 비행기가 더 빨리 날아간다고 말했다.그래서 자전거로 시험해보니 비행기가 더 느리게 움직인다.내가 실험 설정을 어떻게 하는지 알아?
그다지 빠르지는 않지만 테스트 결과 1.6.0_45 64비트를 사용하면 평균 몇 밀리 더 빠른 것으로 나타났습니다.String Builder를 사용하세요.StringBuilder.delete() 대신 setLength(0):
time = System.currentTimeMillis();
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < 10000000; i++) {
sb2.append( "someString" );
sb2.append( "someString2"+i );
sb2.append( "someStrin4g"+i );
sb2.append( "someStr5ing"+i );
sb2.append( "someSt7ring"+i );
a = sb2.toString();
sb2.setLength(0);
}
System.out.println( System.currentTimeMillis()-time );
가장 빠른 방법은 "set Length"를 사용하는 것입니다.복사 작업은 필요 없습니다.새로운 String Builder를 작성하는 방법은 완전히 삭제되어야 합니다.String Builder.delete(int start, int end)의 속도가 느린 것은 크기 조정 부분에 대해 어레이를 다시 복사하기 때문입니다.
System.arraycopy(value, start+len, value, start, count-end);
그 후 StringBuilder.delete()는 StringBuilder.count를 새로운 크기로 업데이트합니다.String Builder가 동작하고 있는 동안.setLength()는 StringBuilder.count를 새 크기로 쉽게 업데이트합니다.
첫 번째는 인간에게 더 좋다.일부 JVM 버전에서 두 번째 버전이 조금 더 빠르면 어떻게 될까요?
성능이 그렇게 중요한 경우 String Builder를 무시하고 직접 작성합니다.만약 당신이 훌륭한 프로그래머라면, 그리고 당신의 앱이 이 기능을 어떻게 사용하고 있는지를 고려한다면, 당신은 그것을 더 빠르게 만들 수 있을 것입니다.보람?아마 아닐 것입니다.
왜 이 질문이 "즐겨찾는 질문"으로 시작되었습니까?성능 최적화는 실용적이든 아니든 매우 즐겁기 때문입니다.
나는 그렇게 성능을 최적화하려고 하는 것이 현명하다고 생각하지 않는다.오늘(2019년) I5 노트북에서 100.000.000 루프에 대해 두 스테이트먼트가 모두 약 11초 실행됩니다.
String a;
StringBuilder sb = new StringBuilder();
long time = 0;
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
StringBuilder sb3 = new StringBuilder();
sb3.append("someString");
sb3.append("someString2");
sb3.append("someStrin4g");
sb3.append("someStr5ing");
sb3.append("someSt7ring");
a = sb3.toString();
}
System.out.println(System.currentTimeMillis() - time);
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
sb.setLength(0);
sb.delete(0, sb.length());
sb.append("someString");
sb.append("someString2");
sb.append("someStrin4g");
sb.append("someStr5ing");
sb.append("someSt7ring");
a = sb.toString();
}
System.out.println(System.currentTimeMillis() - time);
==> 11000밀리초(내부 루프) 및 8236밀리초(외부 루프)
10억 개의 루프가 있는 주소 공개용 프로그램을 실행하고 있다고 해도, 1억 개의 루프가 있는 경우는 2초의 차이가 나지 않습니다.이 프로그램은 몇 시간 동안 실행되고 있기 때문입니다.append 스테이트먼트가1개밖에 없는 경우는 다른 것에 주의해 주세요.
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
StringBuilder sb3 = new StringBuilder();
sb3.append("someString");
a = sb3.toString();
}
System.out.println(System.currentTimeMillis() - time);
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
sb.setLength(0);
sb.delete(0, sb.length());
sb.append("someString");
a = sb.toString();
}
System.out.println(System.currentTimeMillis() - time);
==> 3416밀리초(루프 루프), 3555밀리초(루프 루프)이 경우 루프 내에서 String Builder를 작성하는 첫 번째 문이 더 빠릅니다.또한 실행 순서를 변경하면 훨씬 더 빨라집니다.
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
sb.setLength(0);
sb.delete(0, sb.length());
sb.append("someString");
a = sb.toString();
}
System.out.println(System.currentTimeMillis() - time);
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
StringBuilder sb3 = new StringBuilder();
sb3.append("someString");
a = sb3.toString();
}
System.out.println(System.currentTimeMillis() - time);
==> 3638밀리초(루프 루프), 2908밀리초(루프 루프)
안부 전해 주세요, 울리히
이렇게 많은 새로운 오브젝트를 쉽게 회피할 수 있는 좁은 루프에 재작성하지 않는 방법은 퍼포먼스 벤치마크에서 알 수 있듯이 분명 이점이 있습니다.
그러나 그것은 또한 아무도 언급하지 않은 더 미묘한 이점을 가지고 있다.
이 2차적인 이점은 대규모 앱에서 보았던 애플리케이션 프리즈와 관련된 것으로, CSV 파일을 수백만 줄/레코드, 각 레코드에 약 140개의 필드가 있는 구문 분석 후 생성된 영구 객체를 처리합니다.
여기에서 새 개체를 생성해도 일반적으로 가비지 수집기의 워크로드에는 영향을 주지 않습니다.
앞서 언급한 앱의 수백만 개 레코드의 각 140개 필드를 반복하는 긴밀하게 두 개의 새로운 객체를 만드는 것은 단순히 CPU 사이클만 낭비하는 것이 아닙니다.그것은 GC에 엄청난 부담을 준다.
1,000만 행의 CSV 파일을 구문 분석하여 생성된 객체의 경우 GC에 할당 요청을 받은 후 2 x 140 x 10,000,000 = 28억 개체를 정리합니다!!
사용 가능한 메모리의 양이 부족할 경우(예를 들어 앱이 여러 개의 대용량 파일을 동시에 처리하도록 요청받았을 경우), 실제 작업보다 훨씬 더 많은 GC를 실행하게 될 위험이 있습니다.GC 작업이 CPU 시간의 98% 이상을 차지하면 BANG! 다음 중 하나의 예외 사항이 발생합니다.
GC 오버헤드 제한 초과
https://www.baeldung.com/java-gc-overhead-limit-exceeded
이 경우 매번 새로운 개체를 인스턴스화하는 대신 String Builder와 같은 개체를 재사용하기 위해 코드를 다시 작성하면 (불필요하게 28억 개체를 인스턴스화하지 않음으로써) 많은 GC 액티비티를 회피할 수 있으며, GC 오버헤드 제한 초과 예외가 발생할 가능성을 줄이고 애플리케이션의 일반적인 성능을 대폭 향상시킬 수 있습니다.메모리 용량이 부족하지 않은 경우에도 ormance를 사용할 수 있습니다.
"최적화를 위해 JVM에 맡기는 것"이 모든 시나리오에 적용되는 "경험의 법칙"은 아닙니다.
이미 알려진 큰 입력 파일과 관련된 일종의 메트릭을 사용하여 28억 개의 객체가 불필요하게 생성되는 것을 피하기 위해 코드를 작성하는 사람은 "Pre-Optimizing"이라는 "Puritanicals"에 의해 비난받아서는 안 됩니다.)
두뇌가 반쪽이고 선견지명이 아주 작은 개발자는 예상 입력 파일 크기에 대한 이런 유형의 최적화가 첫날부터 보증된다는 것을 알 수 있었습니다.
한 번 선언하고 매번 할당합니다.이것은 최적화보다 더 실용적이고 재사용 가능한 개념입니다.
언급URL : https://stackoverflow.com/questions/242438/is-it-better-to-reuse-a-stringbuilder-in-a-loop
'itsource' 카테고리의 다른 글
ORDERBY 사용 시 MySQL Slow JOIN 쿼리 (0) | 2022.09.14 |
---|---|
컨텍스트 경로 없이 요청 URI를 얻는 방법 (0) | 2022.09.14 |
목록의 마지막 항목을 삭제하려면 어떻게 해야 합니까? (0) | 2022.09.14 |
콘솔로 이동합니다.writline 및 System.out.println (0) | 2022.09.14 |
JavaScript에서 여러 CSS 스타일을 설정하려면 어떻게 해야 합니까? (0) | 2022.09.14 |