이미 큰 답을 가지고이 Q & A에 늦었지만 메모리의 하위 레벨 관점에서 물건을 보는 데 익숙한 외국인으로 침입하고 싶었습니다.
나는 불변의 디자인, 심지어 C 관점과 오늘날 우리가 가지고있는이 야수적인 하드웨어를 효과적으로 프로그래밍 할 수있는 새로운 방법을 찾는 관점에서 매우 기뻐합니다.
느리게 / 빠르게
속도가 느려지는지에 대한 질문에 대해서는 로봇이 대답합니다 yes
. 이런 종류의 매우 기술적 개념 수준에서 불변성은 일을 더 느리게 할 수 있습니다. 하드웨어는 산발적으로 메모리를 할당하지 않고 기존 메모리를 대신 수정할 수있는 경우에 가장 좋습니다 (시간적 지역 성과 같은 개념을 갖는 이유).
그러나 실질적인 대답은 maybe
입니다. 성능은 여전히 사소한 코드베이스의 생산성 지표입니다. 우리는 일반적으로 버그를 무시하더라도 경쟁 조건을 넘어서는 끔찍한 유지 보수 코드베이스를 가장 효율적으로 찾지 않습니다. 효율성은 종종 우아함과 단순함의 함수입니다. 미세 최적화의 피크는 다소 상충 될 수 있지만 일반적으로 가장 작고 중요한 코드 섹션을 위해 예약되어 있습니다.
불변 비트 및 바이트 변환
우리는 같은 개념 선 X 경우, 낮은 수준의 관점에서 오는 objects
및 strings
그것의 중심, 등등과 서로 다른 속도 / 크기 특성을 갖는 메모리의 다양한 형태 (속도 및 메모리 하드웨어의 크기는 통상적 인 단지 비트 바이트 상호 배타적).
컴퓨터의 메모리 계층은 위의 다이어그램과 같이 동일한 메모리 청크에 반복적으로 액세스 할 때 자주 사용합니다. 자주 액세스하는 메모리 청크를 가장 빠른 형태의 메모리 (L1 캐시)로 유지하기 때문입니다. 거의 레지스터만큼 빠릅니다). 동일한 메모리에 반복적으로 액세스하거나 (여러 번 재사용) 청크의 다른 섹션에 반복적으로 액세스 할 수 있습니다 (예 : 해당 메모리 청크의 다양한 섹션에 반복적으로 액세스하는 연속 청크의 요소를 반복).
이 메모리를 수정하면 다음과 같이 측면에 완전히 새로운 메모리 블록을 만들려고하면 해당 프로세스에서 렌치를 던집니다.
...이 경우, 새로운 메모리 블록에 액세스하려면 강제적 인 페이지 폴트와 캐시 미스가 가장 빠른 형태의 메모리 (레지스터까지)로 다시 이동할 수 있습니다. 실제 성능을 저하시킬 수 있습니다.
그러나 미리 할당 된 메모리의 예약 풀을 사용하여이를 완화 할 수있는 방법이 있습니다.
큰 집계
약간 더 높은 수준의 관점에서 발생하는 또 다른 개념적 문제는 단순히 대량의 집계를 불필요하게 대량 복사하는 것입니다.
지나치게 복잡한 다이어그램을 피하기 위해이 간단한 메모리 블록이 다소 비싸다고 상상해보십시오 (믿을 수 없을 정도로 제한된 하드웨어에서는 UTF-32 문자 일 수 있음).
이 경우 "HELP"를 "KILL"로 바꾸고이 메모리 블록을 변경할 수없는 경우 일부만 변경된 경우에도 완전히 새로운 블록을 만들어 고유 한 새 객체를 만들어야합니다. :
우리의 상상력을 아주 조금 늘리면, 하나의 작은 부분을 독특하게 만들기위한 다른 모든 종류의 깊은 사본은 상당히 비쌀 수 있습니다 (실제 경우이 메모리 블록은 문제를 제기하기에 훨씬 더 클 것입니다).
그러나 이러한 비용에도 불구하고 이러한 종류의 디자인은 인적 오류가 덜 발생하는 경향이 있습니다. 순수한 기능을 가진 기능적 언어로 일한 사람은 아마도 이것을 이해할 수 있습니다. 특히 세계에서 신경 쓰지 않고 그러한 코드를 멀티 스레드 할 수있는 멀티 스레드 경우에 특히 그렇습니다. 일반적으로 휴먼 프로그래머는 상태 변경, 특히 현재 기능 범위 밖의 상태에 외부 부작용을 일으키는 변경을 넘어 트립하는 경향이 있습니다. 이러한 경우 외부 오류 (예외)를 복구하더라도 믹스의 변경 가능한 외부 상태 변경으로 인해 매우 어려울 수 있습니다.
이 중복 복사 작업을 완화하는 한 가지 방법은 이러한 메모리 블록을 다음과 같이 문자에 대한 포인터 (또는 참조) 모음으로 만드는 것입니다.
사과, 나는 L
다이어그램을 만드는 동안 우리가 독특하게 만들 필요가 없다는 것을 깨닫지 못했습니다 .
파란색은 얕은 복사 된 데이터를 나타냅니다.
... 불행히도, 이것은 문자 당 포인터 / 참조 비용을 지불하는 데 엄청나게 비쌀 것입니다. 더욱이, 우리는 주소 공간 전체에 문자의 내용을 분산시켜 페이지 결함 및 캐시 미스의 보트로드 형태로 지불하여 결국 전체 솔루션을 복사하는 것보다이 솔루션을 훨씬 더 악화시킬 수 있습니다.
이 문자들을 연속적으로 할당하려고하더라도, 기계는 문자에 대한 8 개의 문자와 8 개의 포인터를 캐시 라인에로드 할 수 있다고 말합니다. 새 문자열을 순회하기 위해 다음과 같이 메모리를로드합니다.
이 경우 이상적으로는 3 개만 필요할 때이 문자열을 통과하기 위해 7 개의 서로 다른 캐시 라인의 연속 메모리를로드해야합니다.
데이터 청크
위의 문제를 완화하기 위해 동일한 기본 전략을 적용 할 수 있지만 대략 8 자 수준으로 적용 할 수 있습니다.
결과적으로 4 개의 캐시 라인에 해당하는 데이터 (3 개의 포인터에 대해 1 개, 문자에 대해 3 개)를로드하여 이론적으로 최적 인 1 개에 불과한이 문자열을 통과해야합니다.
그래서 전혀 나쁘지 않습니다. 약간의 메모리 낭비가 있지만 메모리가 충분하며 추가 메모리가 자주 액세스되지 않는 차가운 데이터 일 경우 더 많이 사용하더라도 속도가 느려지지 않습니다. 메모리 사용과 속도가 줄어드는 경우가 많고 한 페이지 나 캐시 라인에 더 많은 메모리를 넣고 제거하기 전에 모두 액세스하려는 경우가 많고 연속적인 데이터에만 해당됩니다. 이 표현은 캐시 친화적입니다.
속도
따라서 위와 같은 표현을 사용하면 상당히 균형 잡힌 성능을 얻을 수 있습니다. 변경 불가능한 데이터 구조의 가장 성능이 중요한 사용은 수정되지 않은 조각을 얕게 복사하면서 청크 한 데이터 조각을 수정하고 프로세스에서 고유하게 만드는 특성을 취합니다. 또한 다중 스레드 컨텍스트에서 얕은 복사 된 조각을 안전하게 참조하기 위해 원자 작업에 약간의 오버 헤드가 있음을 의미합니다 (아마도 일부 원자 참조 계산이 진행 중일 수 있음).
그러나이 두툼한 데이터가 대략적으로 충분한 수준으로 표시되는 한, 이러한 오버 헤드가 줄어들고 사소한 경우에도 외부 측면이없는 순수한 형태로 더 많은 기능을 코딩하고 멀티 스레딩하는 안전과 편의성을 제공합니다. 효과.
신규 및 기존 데이터 유지
불변성이 성능 관점에서 잠재적으로 가장 도움이되는 것으로 보았을 때 (실제적인 의미에서) 우리는 목표가 새로운 것을 만들어내는 가변적 인 환경에서 고유하게 만들기 위해 큰 데이터의 전체 사본을 만들려고 할 때입니다 우리는 신중하고 불변의 디자인으로 약간의 비트와 조각을 독특하게 만들 수있을 때 이미 새롭고 오래된 것을 유지하려는 방식으로 존재합니다.
예 : 시스템 실행 취소
이에 대한 예는 실행 취소 시스템입니다. 데이터 구조의 일부를 변경하여 취소 할 수있는 원래 양식과 새 양식을 모두 유지하려고 할 수 있습니다. 데이터 구조의 작고 수정 된 섹션 만 고유하게 만드는 이러한 종류의 불변 디자인으로 추가 된 고유 부분 데이터의 메모리 비용 만 지불하면서 이전 데이터의 사본을 실행 취소 항목에 간단히 저장할 수 있습니다. 이는 생산성 (실행 취소 시스템의 구현을 케이크로 만드는 것)과 성능의 매우 효과적인 균형을 제공합니다.
고급 인터페이스
그러나 위의 경우에는 어색한 것이 발생합니다. 로컬 종류의 함수 컨텍스트에서 변경 가능한 데이터는 종종 가장 쉽고 수정하기 쉬운 것입니다. 결국 배열을 수정하는 가장 쉬운 방법은 종종 배열을 반복하고 한 번에 하나의 요소를 수정하는 것입니다. 배열을 변환하기 위해 선택할 수있는 높은 수준의 알고리즘이 많고 수정 된 부분이 모두 생성되는 동안 이러한 뭉툭한 얕은 복사본이 만들어 지도록 적절한 알고리즘을 선택해야한다면 지적 오버 헤드가 증가 할 수 있습니다. 독창적.
아마도이 경우 가장 쉬운 방법은 변경 불가능한 버퍼를 함수 컨텍스트 (일반적으로 우리를 트립하지 않는)의 컨텍스트 내에서 로컬로 사용하여 변경 사항을 원자 적으로 변경하여 데이터 구조에 원자 적으로 변경하여 새로운 불변 사본을 얻는 것입니다 (일부 언어 호출이라고 생각합니다) 이 "과도 현상") ...
... 또는 단순히 데이터에 대한 상위 및 상위 수준 변환 함수를 모델링하여 변경 가능한 버퍼를 수정하고 변경 가능한 논리없이 구조에 커밋하는 프로세스를 숨길 수 있습니다. 어쨌든, 이것은 아직 광범위하게 탐구 된 영역이 아니며, 이러한 데이터 구조를 변환하는 방법에 대한 의미있는 인터페이스를 찾기 위해 불변 디자인을 더 수용하면 작업이 중단됩니다.
데이터 구조
여기서 발생하는 또 다른 사항은 성능에 중요한 컨텍스트에서 사용되는 불변성이 청크 크기가 너무 작지 않고 너무 크지 않은 데이터 구조를 청크 데이터로 분해하기를 원할 것입니다.
링크 된 목록은이를 수용하기 위해 약간 변경하고 롤링되지 않은 목록으로 전환 할 수 있습니다. 크고 연속적인 배열은 임의 액세스를 위해 모듈로 색인을 사용하여 포인터 배열로 연속 된 덩어리로 바뀔 수 있습니다.
잠재적으로 데이터 구조를 보는 방식을 흥미로운 방식으로 변경하는 한편, 이러한 데이터 구조의 수정 기능을 사용하여 부피를 얕게 복사하여 여기에 약간의 비트를 얕게 복사하고 다른 비트를 고유하게 만드는 복잡성을 숨길 수 있습니다.
공연
어쨌든, 이것은 주제에 대한 저의 작은 관점입니다. 이론적으로, 불변성의 비용은 매우 큰 것에서 작은 것까지의 비용을 가질 수 있습니다. 그러나 매우 이론적 인 접근 방식이 항상 애플리케이션을 빠르게 만드는 것은 아닙니다. 확장 가능하게 만들 수 있지만 실제 속도는 종종보다 실용적인 사고 방식을 수용해야합니다.
실제적인 관점에서 볼 때 성능, 유지 관리 성 및 안전성과 같은 특성은 특히 매우 큰 코드베이스의 경우 한 가지 큰 흐리게 표시되는 경향이 있습니다. 절대적인 의미에서 성능은 불변성으로 인해 저하되지만 생산성 및 안전성 (스레드 안전성 포함)에 대한 이점을 주장하기는 어렵습니다. 개발자가 버그에 얽매이지 않고 코드를 조정하고 최적화 할 시간이 더 많기 때문에 이러한 성능이 향상되면 실제 성능이 향상 될 수 있습니다.
그래서 나는이 실제적인 의미에서, 불변의 데이터 구조가 실제로 있다고 생각 도움 이 소리 홀수로, 많은 경우에 성능을. 이상적인 세계는 불변 데이터 구조와 가변 구조의 혼합을 추구 할 수 있으며, 가변 구조는 일반적으로 매우 로컬 범위 (예 : 함수에 국지)에서 사용하기에 매우 안전하지만 불변의 구조는 외부를 피할 수 있습니다 데이터 구조에 대한 모든 변경 사항을 철저한 영향을 미치고 경쟁 조건의 위험없이 새 버전을 생성하는 원자 작업으로 바꿉니다.