고등학교에서 처음으로 수업을 시작한 이래로 나는 문자열 연산이 신화적인 "평균 연산"보다 느리다는 것, 즉 더 많은 비용이 들었다고 들었습니다. 왜 그렇게 느리게합니까? (이 질문은 의도적으로 광범위하게 남았습니다.)
고등학교에서 처음으로 수업을 시작한 이래로 나는 문자열 연산이 신화적인 "평균 연산"보다 느리다는 것, 즉 더 많은 비용이 들었다고 들었습니다. 왜 그렇게 느리게합니까? (이 질문은 의도적으로 광범위하게 남았습니다.)
답변:
"평균 작업"은 프리미티브에서 발생합니다. 그러나 문자열이 프리미티브로 처리되는 언어에서도 문자열은 여전히 배열 아래에 있으며 전체 문자열과 관련된 모든 작업을 수행하려면 O (N) 시간이 걸립니다. 여기서 N은 문자열의 길이입니다.
예를 들어, 두 개의 숫자를 추가하려면 일반적으로 2-4 ASM 명령이 필요합니다. 두 문자열을 연결 ( "추가")하려면 새로운 메모리 할당과 전체 문자열을 포함하는 하나 또는 두 개의 문자열 복사본이 필요합니다.
특정 언어 요소로 인해 악화 될 수 있습니다. 예를 들어 C에서 문자열은 단순히 널로 끝나는 문자 배열에 대한 포인터입니다. 즉, 길이를 알 수 없으므로 빠른 이동 작업으로 문자열 복사 루프를 최적화 할 수있는 방법이 없습니다. 한 번에 한 문자 씩 복사해야 널 터미네이터의 각 바이트를 테스트 할 수 있습니다.
char*
아니라 strbuf
, 당신은 평방 1. 수있는 좋은 방법입니다 다시 너무 만있다하면 나쁜 디자인이 언어에 구워지면 할 수 있습니다.
buf
포인터가 있습니다. 나는 그것이 가능하지 않다는 것을 암시하지는 않았다. 오히려 필요하다. 표준 라이브러리 와 같은 기본 사항을 포함하여 최적화되었지만 비표준 문자열 유형에 대해 모르는 코드는 여전히 느리고 안전하지 않습니다 char*
. 원하는 경우 해당 FUD를 호출 할 수는 있지만 사실이 아닙니다.
이것은 오래된 스레드이며 다른 답변은 훌륭하지만 무언가를 간과한다고 생각하므로 여기에 2 센트가 있습니다.
문자열의 문제점은 대부분의 언어에서 2 등 시민이며 실제로 대부분의 경우 실제로 언어 사양 자체의 일부가 아니라는 점입니다. 즉, 때때로 문법적으로 설탕이 코팅 된 라이브러리 구현 구문입니다. 고통을 덜어주기 위해
이것의 직접적인 결과는 언어가 복잡한 것의 많은 부분을 시야에서 멀리 숨기고, 저수준의 원자 실체처럼 생각하는 습관으로 자라기 때문에 부적절한 부작용에 대한 비용을 지불한다는 것입니다. 다른 기본 유형 (최고의 답변 및 기타에 의해 설명 됨).
이 기본 "복잡성"의 요소 중 하나는 대부분의 문자열 구현이 문자열을 나타내는 인접한 메모리 공간이있는 간단한 데이터 구조를 사용한다는 것입니다.
문자열 전체에 대한 액세스가 빠르기를 원하기 때문에이 점이 좋습니다. 그러나 이것은이 문자열을 조작 할 때 잠재적으로 끔찍한 비용을 의미합니다. 어떤 인덱스를 사용하고 있는지 알면 중간에 요소에 액세스하는 것이 빠를 수 있지만 조건에 따라 요소를 찾는 것은 그렇지 않습니다.
언어가 문자열 길이를 캐시하지 않고 문자 수를 계산하기 위해 문자열을 실행 해야하는 경우 문자열 크기를 반환하더라도 비용이 많이들 수 있습니다.
비슷한 이유로 문자열에 요소를 추가 하면이 작업을 수행하기 위해 메모리를 다시 할당해야 할 가능성이 높으므로 비용이 많이 듭니다.
따라서 언어마다 다른 문제가 있습니다. 예를 들어, Java는 유효한 이유 (캐싱 길이, 스레드 안전성)로 인해 문자열을 변경할 수없는 자유를 가져 왔고 변경 가능한 대응 물 (StringBuffer 및 StringBuilder)은 더 큰 크기의 청크를 사용하여 크기를 할당 할 필요가 없습니다. 항상 최선의 시나리오를 희망합니다. 일반적으로 잘 작동하지만 단점은 때로는 메모리 영향을 지불하는 것입니다.
또한, 이것은 언어의 구문 설탕 코팅이 이것을 즐기기 위해 숨기고 있기 때문에 종종 유니 코드 지원이라는 용어를 생각하지 않습니다 (특히 실제로 필요하지 않는 한) 벽에 부딪쳤다). 그리고 미래를 생각하는 일부 언어는 간단한 8 비트 문자 프리미티브의 기본 배열로 문자열을 구현하지 않습니다. 그것들은 UTF-8 또는 UTF-16으로 구워 졌거나 당신을 위해 무엇을 지원 했습니까? 결과적으로 엄청나게 많은 메모리 소비가 종종 필요하지 않으며 메모리를 할당하고 문자열을 처리하는 데 더 많은 처리 시간이 필요합니다. 코드 포인트 조작과 함께 모든 논리를 구현합니다.
이 모든 결과는 의사 코드에서 동등한 작업을 수행 할 때 다음과 같습니다.
hello = "hello,"
world = " world!"
str = hello + world
언어 개발자가 최선의 노력을 기울 였음에도 불구하고 다음과 같은 간단한 동작을 제외하고는 그렇지 않을 수도 있습니다.
a = 1;
b = 2;
shouldBeThree = a + b
후속 조치로 다음을 읽을 수 있습니다.
"평균 연산"이라는 문구는 이론적 랜덤 액세스 저장 프로그램 머신 의 단일 조작을위한 축약 형일 것 입니다 . 이것은 다양한 알고리즘의 실행 시간을 분석하는 데 일반적으로 사용되는 이론적 인 시스템입니다.
일반 작업은 일반적으로로드, 더하기, 빼기, 저장, 분기로 이루어집니다. 아마도 읽고 인쇄하고 멈출 수도 있습니다.
그러나 대부분의 문자열 연산에는 이러한 몇 가지 기본 연산이 필요합니다. 예를 들어, 문자열을 복제하려면 일반적으로 복사 작업이 필요하므로 문자열 길이에 비례하는 많은 작업 (즉, "선형")이 필요합니다. 다른 문자열에서 하위 문자열을 찾는 것도 선형 복잡성입니다.
작업, 문자열 표현 방법 및 존재하는 최적화에 따라 달라집니다. 문자열의 길이가 4 또는 8 바이트이고 정렬되는 경우 반드시 느릴 필요는 없습니다. 많은 연산이 프리미티브만큼 빠릅니다. 또는 모든 문자열에 32 비트 또는 64 비트 해시가있는 경우 많은 작업이 빠릅니다 (해시 비용을 선불로 지불하더라도).
또한 "느리게"의 의미에 따라 다릅니다. 대부분의 프로그램은 필요한만큼 문자열을 빠르게 처리합니다. 문자열 비교는 두 정수를 비교하는 것만 큼 빠르지는 않지만 프로파일 링만으로 "느린"것이 프로그램에 어떤 의미인지 알 수 있습니다.