문자열을 한 번에 하나씩 연결하는 것이 비효율적입니까?


11

나는 C에서 프로그래밍 한 두 시절을 회상하여 두 개의 문자열이 결합되면 OS가 결합 된 문자열에 대한 메모리를 할당해야하며 프로그램은 모든 문자열 텍스트를 메모리의 새 영역으로 복사 한 다음 이전 메모리를 수동으로 복사해야합니다. 풀린다. 따라서 목록에 가입 할 때처럼 여러 번 수행하면 OS는 다음 연결 후에 해제하기 위해 점점 더 많은 메모리를 지속적으로 할당해야합니다. C 에서이 작업을 수행하는 더 좋은 방법은 결합 된 문자열의 총 크기를 결정하고 결합 된 전체 문자열 목록에 필요한 메모리를 할당하는 것입니다.

이제 현대 프로그래밍 언어 (예 : C #)에서는 컬렉션을 반복하고 한 번에 하나씩 모든 문자열을 단일 문자열 참조에 추가하여 컬렉션의 내용이 결합되는 것을 흔히 볼 수 있습니다. 현대적인 컴퓨팅 기능을 사용해도 비효율적이지 않습니까?


컴파일러와 프로파일 러에 맡기면 걱정할 것입니다. 문자열 연결 시간보다 훨씬 비쌉니다.
OZ_

7
구현에 따라-특정 문자열 라이브러리에 대한 설명서를 실제로 확인해야합니다. O (1) 시간에 참조로 연결되는 문자열을 구현할 수 있습니다. 어쨌든 임의의 긴 문자열 목록을 연결해야하는 경우 이러한 종류의 작업을 위해 설계된 클래스 또는 함수를 사용해야합니다.
오는 폭풍

문자열 연결과 같은 것은 일반적으로 운영 체제가 아닌 라이브러리 함수에 의해 처리됩니다. OS는 메모리 할당에 관여 할 수 있지만 문자열과 같은 상대적으로 작은 객체에는 적합하지 않을 수 있습니다.
Caleb

@Caleb OS는 모든 메모리 할당에 관여합니다. 이 규칙을 따르지 않으면 메모리 누수가 발생합니다. 응용 프로그램에 하드 코딩 된 문자열이있는 경우는 예외입니다. 생성 된 어셈블리 내에서 이진 데이터로 작성됩니다. 그러나 문자열을 조작 (또는 할당)하는 즉시 문자열을 메모리에 저장해야합니다 (즉, 메모리를 할당해야 함).
JSideris

4
@Bizorke 일반적인 시나리오에서 malloc ()과 같은 메모리 할당 자 (OS가 아닌 C 표준 라이브러리의 일부)는 이미 OS에서 프로세스에 할당 한 메모리에서 다양한 메모리 청크를 할당하는 데 사용됩니다. 프로세스의 메모리가 부족하여 더 많은 것을 요구하지 않는 한 OS는 관여 할 필요가 없습니다. 할당으로 인해 페이지 오류가 발생할 경우 하위 수준에서 참여할 수도 있습니다. 따라서 OS는 궁극적으로 메모리를 제공하지만 프로세스 내에서 문자열 및 기타 객체의 부분 할당에 반드시 관여하지는 않습니다.
Caleb

답변:


21

비효율적 인 이유에 대한 설명은 적어도 (C, Java, C #)에 익숙한 언어에서는 정확하지만 대량의 문자열 연결을 수행하는 것이 일반적으로 동의하지는 않습니다. 내가 작업하고 C # 코드에서의 풍부한 사용이 StringBuilder, String.Format이상 - 재 할당을 피하기 위해 techiniques을 저장하는 모든 메모리이다 등.

따라서 질문에 대한 답을 얻으려면 다른 질문을해야합니다. 문자열을 연결하는 것이 실제로 문제가되지 않으면 왜 클래스가 좋아 StringBuilder하고 StringBuffer존재 합니까? 세미 클래스 프로그래밍 서적과 클래스에도 그러한 클래스의 사용이 포함되는 이유는 무엇입니까? 조기 최적화 조언이 그렇게 두드러진 이유는 무엇입니까?

대부분의 문자열 연결 개발자가 자신의 답변을 순수한 경험에 기반을 둔다면 대부분은 차이를 만들지 않으며 "더 읽기 쉬운"을 위해 이러한 도구를 사용하지 않을 것이라고 말합니다 for (int i=0; i<1000; i++) { strA += strB; }. 그러나 그들은 그것을 측정하지 않았습니다.

이 질문에 대한 실제 답변은 이 SO 답변 에서 찾을 수 있습니다. 경우 한 번에 50,000 개의 문자열 (응용 프로그램에 따라 공통적 일 수 있음)을 연결할 때 작은 문자열조차도 1000 배의 성능 저하를 초래했습니다 .

문자 그대로 성능이 전혀 의미가없는 경우에는 반드시 연결하십시오. 그러나 대안 (StringBuilder)을 사용하는 것이 어렵거나 읽기 쉽지 않기 때문에 "조기 최적화"방어를 호출해서는 안되는 합리적인 프로그래밍 방법이 될 것이라고 동의하지 않습니다.

최신 정보:

나는 이것이 당신의 플랫폼을 알고 최선의 관행을 따르는 것이라고 생각합니다 . 슬프게도 보편적이지 않습니다 . 서로 다른 두 "현대 언어"의 두 가지 예 :

  1. 다른 SO 답변 에서 정확한 반대 성능 특성 (array.join vs + =)이 때때로 JavaScript 에서 사실 로 밝혀졌습니다 . 일부 브라우저에서 문자열 연결은 자동으로 최적화 된 것으로 보이며 그렇지 않은 경우에는 그렇지 않습니다. 따라서 (적어도 SO 질문에서) 권장 사항은 연결하고 걱정하지 않는 것입니다.
  2. 다른 경우, Java 컴파일러 연결을 StringBuilder와 같은보다 효율적인 구성으로 자동 대체 할 수 있습니다 . 그러나 다른 사람들이 지적했듯이 이것은 결정적이지 않으며 보장되지 않으며 StringBuilder를 사용하더라도 가독성이 손상되지 않습니다. 이 특별한 경우에는 큰 컬렉션에 연결을 사용하거나 결정적이지 않은 Java 컴파일러 동작에 의존하지 않는 것이 좋습니다. 마찬가지로 .NET에서는 정렬 최적화가 수행되지 않습니다 .

모든 플랫폼의 모든 뉘앙스를 즉시 알지 못하는 것이 정확히 중요한 죄는 아니지만, 이와 같은 중요한 플랫폼 문제를 무시하는 것은 거의 Java에서 C ++로 이동하고 메모리 할당 해제에 신경 쓰지 않는 것과 거의 같습니다.


-1 : 주요 BS를 포함합니다. strA + strB입니다 정확히 StringBuilder 내를 사용하는 것과 같습니다. 성능이 1 배나 높습니다. 또는 측정 방식에 따라 0x. 자세한 내용은 codinghorror.com/blog/2009/01/…
amara

5
@ sparkleshy : 내 생각에 SO 답변은 Java를 사용하고 링크 된 기사는 C #을 사용합니다. "구현에 달려있다"고 "특정 환경에 맞게 측정"하는 사람들에 동의합니다.
Kai Chan

1
@KaiChan : 문자열 연결은 기본적으로 java와 c #에서 동일합니다
amara

3
@sparkleshy-포인트를 얻었지만 StringBuilder, String.Join 등을 사용하여 정확히 두 문자열을 연결하는 것은 권장 사항이 아닙니다. 또한 OP의 질문은 구체적으로 " 함께 결합되는 컬렉션 의 내용"과 관련이 있으며, StringBuilder 등이 매우 적용 가능한 경우는 아닙니다. 어쨌든, 나는 예제를 더 중요하게 업데이트 할 것입니다.
Kevin McCormick

3
이 질문의 목적을 위해 언어에 관심이 없습니다. 일부 언어 에서 배후에서 stringbuilder를 사용 하면 전체 문자열 목록을 연결하는 것이 비효율적이지 않은 이유를 설명하여 내 질문에 대답합니다. 그러나이 답변은 목록에 참여하는 것이 잠재적으로 위험 할 수 있으며 대안으로 stringbuilder를 권장한다고 설명했습니다. 평판 손실이나 오해의 가능성을 피하기 위해 컴파일러의 장면 뒤에 stringbuilder 사용을 답변에 추가하는 것이 좋습니다.
JSideris

2

대략 당신이 설명 한 이유로 비효율적입니다. C # 및 Java의 문자열은 변경할 수 없습니다. 문자열에 대한 작업은 C와 달리 원래 인스턴스를 수정하는 대신 별도의 인스턴스를 반환합니다. 여러 문자열을 연결하면 각 단계에서 별도의 인스턴스가 생성됩니다. 할당되지 않은 인스턴스를 할당 한 후 나중에 가비지 수집하면 성능이 저하 될 수 있습니다. 이번에는 메모리 관리 만 가비지 수집기에서 처리합니다.

C #과 Java는 모두 이러한 유형의 작업을 위해 StringBuilder 클래스를 변경 가능한 문자열로 도입합니다. C와 동등한 것은 연결된 문자열을 배열로 묶는 대신 연결된 목록을 사용하는 것입니다. C #은 문자열 컬렉션에 조인하기 위해 문자열에 편리한 Join 메서드도 제공합니다.


1

엄밀히 말하면 CPU 사이클을 덜 효율적으로 사용하므로 정확합니다. 그러나 개발자 시간, 유지 보수 비용 등은 어떻습니까? 방정식에 시간 비용을 추가하면 가장 쉬운 방법을 수행하는 것이 가장 효율적이며, 필요한 경우 느린 비트를 프로파일 링하고 최적화하는 것이 거의 항상 효율적입니다.
"프로그램 최적화의 첫 번째 규칙 :하지 마십시오. 프로그램 최적화의 두 번째 규칙 (전문가에게만 해당) : 아직하지 마십시오."


3
매우 효과적인 규칙은 아닙니다.
OZ_

@OZ_ : 이것은 널리 사용되는 인용문 (Michael A. Jackson)과 도널드 크 누스 (Donald Knuth)와 같은 사람들에 의해 ... 다른 사람은 이것을 사용하지 않는 것이 좋습니다. 맹목적인 어리 석음을 포함하여 다른 어떤 단일 이유보다 반드시 달성하지 않아도됩니다. "
mattnz

2
나는 것을 지적한다 마이클 A. 잭슨 브릿, 그래서 그것의이었다 최적화 되지 최적화 . 어느 시점에서 나는 위키 백과 페이지를 정말로 수정해야한다 . * 8 ')
Mark Booth

동의합니다. 철자 오류를 수정해야합니다. 내 모국어는 Queens English이지만 웹에서 미국 말을하는 것이 더 쉽다는 것을 알게되었습니다 ..
mattnz

누군가 사용자를 생각하지 않습니다. 개발자가 약간 더 빠르게 만들 수는 있지만 모든 고객이 어려움을 겪습니다. 당신을 위해서가 아니라 그들을 위해 코드를 작성하십시오.
gbjbaanb

1

실제적인 테스트 없이는 성능에 대해 말하기가 매우 어렵습니다. 최근 JavaScript에서 순진한 문자열 연결이 일반적으로 권장되는 "목록 작성 및 조인"솔루션보다 빠르다는 사실에 놀랐습니다 ( 여기서는 t1과 t4 비교). 나는 왜 그런 일이 일어나는지 아직도 의아해합니다.

성능 (특히 메모리 사용에 관한)을 추론 할 때 물어볼 수있는 몇 가지 질문은 다음과 같습니다. 1) 입력이 얼마나 큽니까? 2) 내 컴파일러는 얼마나 똑똑합니까? 3) 런타임은 메모리를 어떻게 관리합니까? 이것은 철저하지는 않지만 시작점입니다.

  1. 입력이 얼마나 큽니까?

    복잡한 솔루션은 종종 수행해야 할 추가 작업의 형태 또는 추가 메모리가 필요한 고정 된 오버 헤드를 갖습니다. 이러한 솔루션은 큰 경우를 처리하도록 설계되었으므로 코드를 미세 최적화하는 데 순 이득이 더 중요하기 때문에 구현자는 일반적으로 추가 비용을 발생시키는 데 아무런 문제가 없습니다. 따라서 입력이 충분히 작은 경우이 오버 헤드를 피하기 위해 순진 솔루션이 복잡한 솔루션보다 성능이 더 우수 할 수 있습니다. ( "충분히 작은"것을 결정하는 것은 어려운 부분입니다)

  2. 컴파일러는 얼마나 똑똑합니까?

    많은 컴파일러는 작성되었지만 읽지 않는 변수를 "최적화"할 수있을 정도로 똑똑합니다. 마찬가지로, 좋은 컴파일러는 순진한 문자열 연결을 (핵심) 라이브러리 사용으로 변환 할 수 있으며, 대부분이 읽지 않고 작성된 경우 해당 연산 사이에서 문자열로 다시 변환 할 필요가 없습니다. 소스 코드가 그렇게하는 것 같습니다). 나는 어떤 컴파일러가 그렇게하는지, 또는 어느 정도까지 수행되는지 (AFAIK Java는 적어도 동일한 표현에서 여러 개의 concat을 StringBuffer 연산 시퀀스로 대체) 알 수 없지만 가능성이 있습니다.

  3. 런타임은 메모리를 어떻게 관리합니까?

    최신 CPU에서 병목 현상은 일반적으로 프로세서가 아니라 캐시입니다. 코드가 짧은 시간에 많은 "원격"메모리 주소에 액세스하는 경우 캐시 수준 사이에서 모든 메모리를 이동하는 데 걸리는 시간은 사용 된 지침에서 대부분의 최적화보다 중요합니다. 가장 최근에 생성 된 변수 (예 : 동일한 함수 범위 내)가 일반적으로 인접한 메모리 주소에 있기 때문에 세대 가비지 콜렉터가있는 런타임에서 특히 중요합니다. 이러한 런타임은 또한 정기적으로 메소드 호출간에 메모리를 앞뒤로 이동시킵니다.

    그것이 문자열 연결에 영향을 줄 수있는 한 가지 방법 (면책 조항 : 이것은 명백한 추측입니다. 확실히 말할만큼 지식이 없습니다) 순진한 메모리가 메모리를 사용하는 나머지 코드에 가깝게 할당 된 경우입니다. 라이브러리 객체에 대한 메모리가 멀리 떨어져 할당 된 동안 (코드가 계산되고, 라이브러리가 소비하고, 코드가 더 많이 계산되는 등 많은 캐시 누락이 발생할 수 있습니다.) 많은 컨텍스트가 변경됩니다. 물론 큰 입력 OTOH의 경우 캐시 누락이 발생하므로 여러 할당 문제가 더 두드러집니다.

즉,이 방법이나 그 방법의 사용을 옹호하지는 않습니다. 오늘날 대부분의 시스템은 주제에 대한 깊은 전문 지식 없이는 완전히 이해하기에는 너무 복잡하기 때문에 테스트 및 프로파일 링 및 벤치마킹 만이 성능에 대한 이론적 분석보다 우선합니다.


그러나 나는 이것이 컴파일러가 이론적으로 문자열 묶음을 더한 다음 문자열 작성기를 사용하는 것처럼 최적화하려고한다는 것을 이론적으로 알 수있는 영역이라는 데 동의합니다. 그러나 이것은 사소한 일이 아니며 현대 컴파일러에서 구현되지 않았다고 생각합니다. 당신은 저에게 학부 연구 프로젝트에 대한 좋은 아이디어를주었습니다 : D.
JSideris

이 답변을 확인하십시오 .Java 컴파일러는 이미 StringBuilder후드에서 사용 toString하고 있습니다. 변수가 실제로 필요할 때까지 호출 하면됩니다. 내가 올바르게 기억, 그것은 않는 하나의 표현, 내 유일한 의심의 여지가이 같은 방법으로 여러 문에 적용할지 여부입니다. .NET 내부에 대해서는 아무것도 모르지만 C # 컴파일러에서도 비슷한 전략을 사용할 수 있다고 생각합니다.
mgibsonbr

0

요엘은 이 주제에 관해 얼마 전에 훌륭한 기사 를 썼습니다 . 다른 사람들이 지적했듯이 언어에 크게 의존합니다. 문자열이 C로 구현되는 방식 (길이 필드없이 종료되는 0)으로 인해 표준 strcat 라이브러리 루틴은 매우 비효율적입니다. Joel은 훨씬 더 효율적인 사소한 변경으로 대안을 제시합니다.


-1

문자열을 한 번에 하나씩 연결하는 것이 비효율적입니까?

아니.

'마이크로 최적화 극장의 슬픈 비극' 을 읽었 습니까?


4
"조기 최적화는 모든 악의 근원입니다." -크 누스
스콧 C 윌슨

4
최적화의 모든 악의 뿌리는 문맥 없이이 문구를 취합니다.
OZ_

일부 지원 사유를 제공하지 않고 무언가를 말하는 것만으로는 이와 같은 포럼에서 유용하지 않습니다.
Edward Strange

@Crazy Eddie : Jeff Atwood가 왜 말해야하는지 읽었습니까?
Jim G.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.