programing tip

공유 메모리 대 메시지 전달은 대용량 데이터 구조를 어떻게 처리합니까?

itbloger 2020. 12. 25. 09:02
반응형

공유 메모리 대 메시지 전달은 대용량 데이터 구조를 어떻게 처리합니까?


Go와 Erlang의 동시성 접근 방식을 살펴보면 둘 다 메시지 전달에 의존한다는 것을 알았습니다.

이 접근 방식은 공유 상태가 없기 때문에 복잡한 잠금의 필요성을 분명히 줄여줍니다.

그러나 접미사 배열과 같이 메모리의 단일 대형 데이터 구조에 병렬 읽기 전용 액세스를 원하는 많은 클라이언트의 경우를 고려하십시오.

내 질문 :

  • 공유 상태를 사용하는 것이 메시지 전달보다 더 빠르고 메모리를 덜 사용합니까? 데이터가 읽기 전용이고 단일 위치에만 존재하면되기 때문에 잠금이 대부분 불필요하기 때문입니까?

  • 메시지 전달 컨텍스트에서이 문제에 어떻게 접근 할 수 있습니까? 데이터 구조에 액세스 할 수있는 단일 프로세스가 있고 클라이언트는 단순히 데이터를 순차적으로 요청하면됩니까? 또는 가능하다면 데이터를 청크하여 청크를 보유하는 여러 프로세스를 생성합니까?

  • 최신 CPU 및 메모리의 아키텍처를 감안할 때 두 솔루션 사이에 많은 차이가 있습니까? 즉, 공유 메모리를 여러 코어에서 병렬로 읽을 수 있습니다. 그렇지 않으면 두 구현이 대략 동일하게 수행되는 하드웨어 병목 현상이 없습니다.


  • 예,이 경우 공유 상태가 더 빠를 수 있습니다. 그러나 잠금을 잊을 수있는 경우에만 가능하며 이는 절대적으로 읽기 전용 일 때만 가능합니다. '대부분 읽기 전용'인 경우 잠금이 필요합니다 (잠금없는 구조를 작성할 수있는 경우가 아니면 잠금보다 더 까다 롭다는 경고를받습니다). 좋은 메시지 전달 아키텍처만큼 빠릅니다.

  • 예, 공유 할 '서버 프로세스'를 작성할 수 있습니다. 매우 가벼운 프로세스를 사용하면 데이터에 액세스하기 위해 작은 API를 작성하는 것보다 무겁지 않습니다. 데이터를 '소유'하는 객체 (OOP 의미)처럼 생각하십시오. 병렬성을 향상시키기 위해 데이터를 청크로 분할 (DB 서클에서 '샤딩'이라고 함)하면 큰 경우 (또는 데이터가 느린 스토리지에있는 경우)에 도움이됩니다.

  • NUMA가 주류가 되더라도 NUMA 셀당 점점 더 많은 코어가 있습니다. 그리고 큰 차이점은 메시지가 두 개의 코어 사이에서만 전달 될 수있는 반면 잠금은 모든 코어의 캐시에서 플러시되어 셀 간 버스 대기 시간 (RAM 액세스보다 느림)으로 제한되어야한다는 것입니다. 무엇이든 공유 상태 / 잠금은 점점 더 실현 불가능 해지고 있습니다.

간단히 말해서 .... 메시지 전달 및 서버 프로세스에 익숙해지면 모든 것이 분노합니다.

편집 :이 답변을 다시 방문하여 Go의 문서에서 찾은 문구에 대해 추가하고 싶습니다.

의사 소통을 통해 메모리를 공유하고 메모리를 공유하여 의사 소통하지 마십시오.

아이디어는 스레드간에 메모리 블록을 공유 할 때 동시 액세스를 방지하는 일반적인 방법은 잠금을 사용하여 중재하는 것입니다. Go 스타일은 참조와 함께 메시지를 전달하는 것이며 스레드는 메시지를 수신 할 때만 메모리에 액세스합니다. 그것은 어느 정도의 프로그래머 규율에 의존합니다. 그러나 쉽게 교정 할 수있는 매우 깔끔한 코드가 생성되므로 비교적 디버그하기 쉽습니다.

장점은 모든 메시지에 큰 데이터 블록을 복사 할 필요가 없으며 일부 잠금 구현 에서처럼 캐시를 효과적으로 플러시 할 필요가 없다는 것입니다. 스타일이 더 높은 성능의 디자인으로 이어질지 여부를 말하기는 아직 다소 이르다. (특히 현재 Go 런타임은 스레드 스케줄링에 다소 순진하기 때문에)


실현에 한 가지가 얼랑 동시성 모델 않는다는 것입니다 NOT 정말 메시지의 데이터가 프로세스간에 복사해야합니다 지정, 그것은 메시지를 보내는 통신 할 수있는 유일한 방법은 없다는 것을 더 상태가 공유 상태. 모든 데이터는 불변 이므로 기본적으로 구현시 데이터를 복사하지 않고 참조를 보낼 수 있습니다. 또는 두 방법의 조합을 사용할 수 있습니다. 항상 그렇듯이 최상의 솔루션 은 없으며 이를 수행하는 방법을 선택할 때 고려해야 할 절충안이 있습니다.

BEAM은 참조를 보내는 큰 바이너리를 제외하고 복사를 사용합니다.


Erlang에서 모든 값은 변경할 수 없습니다. 따라서 프로세스간에 메시지를 보낼 때 메시지를 복사 할 필요가 없습니다. 어쨌든 수정할 수 없습니다.

Go에서 메시지 전달은 관례에 따릅니다. 누군가에게 채널 위에 포인터를 보낸 다음 가리키는 데이터를 수정하는 것을 막을 수있는 것은 없습니다. 관례 일 뿐이므로 다시 한 번 메시지를 복사 할 필요가 없습니다.


대부분의 최신 프로세서는 MESI 프로토콜의 변형을 사용 합니다 . 공유 상태로 인해 서로 다른 스레드간에 읽기 전용 데이터를 전달하는 것은 매우 저렴합니다. 수정 된 공유 데이터는이 캐시 라인을 저장하는 다른 모든 캐시가이를 무효화해야하기 때문에 매우 비쌉니다.

따라서 읽기 전용 데이터가있는 경우 메시지와 함께 복사하는 대신 스레드간에 공유하는 것이 매우 저렴합니다. 대부분의 데이터를 읽는 경우 스레드간에 공유하는 데 비용이 많이들 수 있습니다. 부분적으로는 액세스를 동기화해야하기 때문이며, 부분적으로는 쓰기가 공유 데이터의 캐시 친화적 동작을 파괴하기 때문입니다.

변경 불가능한 데이터 구조 는 여기에서 유용 할 수 있습니다. 실제 데이터 구조를 변경하는 대신 대부분의 이전 데이터를 공유하는 새 구조를 만들지 만 변경해야하는 사항은 변경하면됩니다. 모든 데이터를 변경할 수 없기 때문에 단일 버전을 공유하는 것은 저렴하지만 새 버전으로 효율적으로 업데이트 할 수 있습니다.


메시지 전달은 공유 상태를 사용할 수 있기 때문에 귀하의 질문은 기술적으로 무의미합니다. 따라서 공유 상태를 피하기 위해 메시지 전달을 의미한다고 가정합니다 (현재 Erlang이 그렇듯이).

공유 상태를 사용하는 것이 메시지 전달보다 더 빠르고 메모리를 덜 사용합니까? 데이터가 읽기 전용이고 단일 위치에만 존재하면되기 때문에 잠금이 대부분 불필요하기 때문입니까?

공유 상태를 사용하면 훨씬 빠릅니다.

메시지 전달 컨텍스트에서이 문제에 어떻게 접근 할 수 있습니까? 데이터 구조에 액세스 할 수있는 단일 프로세스가 있고 클라이언트는 단순히 데이터를 순차적으로 요청하면됩니까? 또는 가능하다면 데이터를 청크하여 청크를 보유하는 여러 프로세스를 생성합니까?

두 방법 모두 사용할 수 있습니다.

최신 CPU 및 메모리의 아키텍처를 감안할 때 두 솔루션 사이에 많은 차이가 있습니까? 즉, 공유 메모리를 여러 코어에서 병렬로 읽을 수 있습니다. 그렇지 않으면 두 구현이 대략 동일하게 수행되는 하드웨어 병목 현상이 없습니다.

복사는 캐시에 비 친화적이므로 주 메모리 인 공유 리소스에 대한 경합을 악화 시키므로 멀티 코어의 확장 성이 손상됩니다.

궁극적으로 Erlang 스타일 메시지 전달은 동시 프로그래밍을 위해 설계되었지만 처리량 성능에 대한 질문은 실제로 병렬 프로그래밍을 대상으로합니다. 이것들은 완전히 다른 두 가지 주제이며 실제로 그들 사이의 겹침은 작습니다. 특히, 대기 시간은 일반적으로 동시 프로그래밍의 맥락에서 처리량만큼 중요하며 Erlang 스타일 메시지 전달은 바람직한 대기 시간 프로필 (즉, 지속적으로 낮은 대기 시간)을 달성하는 좋은 방법입니다. 공유 메모리의 문제는 판독기와 작성자 간의 동기화가 아니라 지연 시간이 짧은 메모리 관리입니다.


대용량 데이터 구조 란 무엇입니까 ?

큰 사람과 작은 사람이 있습니다.

지난주에 저는 두 사람과 이야기를 나눴습니다. 한 사람은 임베디드 장치를 만들고 "대형"이라는 단어를 사용했습니다. 그게 무슨 뜻인지 물었습니다. 256KB 이상이라고 말했습니다. 같은 주 후반에 한 남자가 미디어 배포에 대해 이야기했습니다. "대형"이라는 단어가 그에게 무슨 뜻인지 물었습니다. 그는 잠시 생각하고 "한 대의 기계에 맞지 않습니다"라고 말했습니다. 20-100 TBytes

Erlang 용어에서 "큰"은 "RAM에 맞지 않음"을 의미 할 수 있습니다. 따라서 4GB의 RAM 데이터 구조> 100MB는 큰 것으로 간주 될 수 있습니다. 500MB의 데이터 구조를 복사하는 것은 문제가 될 수 있습니다. 작은 데이터 구조 (예 : 10MB 미만)를 복사하는 것은 Erlang에서 문제가되지 않습니다.

매우 큰 데이터 구조 (즉, 한 시스템에 맞지 않는 구조)는 여러 시스템에 걸쳐 복사 및 "스트라이핑"되어야합니다.

그래서 나는 당신이 다음을 가지고 있다고 생각합니다.

Small data structures are no problem - since they are small data processing times are fast, copying is fast and so on (just because they are small)

Big data structures are a problem - because they don't fit on one machine - so copying is essential.


One solution that has not been presented here is master-slave replication. If you have a large data-structure, you can replicate changes to it out to all slaves that perform the update on their copy.

This is especially interesting if one wants to scale to several machines that don't even have the possibility to share memory without very artificial setups (mmap of a block device that read/write from a remote computer's memory?)

A variant of it is to have a transaction manager that one ask nicely to update the replicated data structure, and it will make sure that it serves one and only update-request concurrently. This is more of the mnesia model for master-master replication of mnesia table-data, which qualify as "large data structure".


The problem at the moment is indeed that the locking and cache-line coherency might be as expensive as copying a simpler data structure (e.g. a few hundred bytes).

Most of the time a clever written new multi-threaded algorithm that tries to eliminate most of the locking will always be faster - and a lot faster with modern lock-free data structures. Especially when you have well designed cache systems like Sun's Niagara chip level multi-threading.

If your system/problem is not easily broken down into a few and simple data accesses then you have a problem. And not all problems can be solved by message passing. This is why there are still some Itanium based super computers sold because they have terabyte of shared RAM and up to 128 CPU's working on the same shared memory. They are an order of magnitude more expensive then a mainstream x86 cluster with the same CPU power but you don't need to break down your data.

Another reason not mentioned so far is that programs can become much easier to write and maintain when you use multi-threading. Message passing and the shared nothing approach makes it even more maintainable.

As an example, Erlang was never designed to make things faster but instead use a large number of threads to structure complex data and event flows.

I guess this was one of the main points in the design. In the web world of google you usually don't care about performance - as long as it can run in parallel in the cloud. And with message passing you ideally can just add more computers without changing the source code.


Usually message passing languages (this is especially easy in erlang, since it has immutable variables) optimise away the actual data copying between the processes (of course local processes only: you'll want to think your network distribution pattern wisely), so this isn't much an issue.


The other concurrent paradigm is STM, software transactional memory. Clojure's ref's are getting a lot of attention. Tim Bray has a good series exploring erlang and clojure's concurrent mechanisms

http://www.tbray.org/ongoing/When/200x/2009/09/27/Concur-dot-next

http://www.tbray.org/ongoing/When/200x/2009/12/01/Clojure-Theses

ReferenceURL : https://stackoverflow.com/questions/1798455/how-does-shared-memory-vs-message-passing-handle-large-data-structures

반응형