저는 경량 프로세스를 실험해 왔습니다. 기본적으로 복제 기능이 호출되고 복제된 LWP에 새 PID가 할당됩니다. 이는 훌륭하게 작동합니다. 이를 통해 해당 LWP의 모든 하위 스레드를 식별할 수 있습니다. 내가 가진 작은 문제는 성능입니다. 성능이 많이 떨어졌습니다(처리 속도가 30% 느려졌습니다). 이제 나는 LWP를 예약하고 우선순위를 할당하는 것이 가능하다는 것을 읽었습니다(나도 그것을 시도하지 않았습니다). 이것이 성능에 도움이 됩니까?
strace를 실행할 때 제가 느낀 점 중 하나는 Futex 사용량이 8~10배 폭발적으로 증가했다는 것입니다. LWP가 이것의 주된 이유가 될 수 있습니까? 이해할 수 있는 부분은 컨텍스트 전환이 폭발적으로 증가한 것이지만, LWP는 동일한 메모리 공간을 공유하므로 Futex 사용으로 인해 폭발이 일어나서는 안 된다고 생각합니다.
LWP를 사용하거나 사용하기로 결정할 때 따라야 할 팁이나 모범 사례가 있습니까?
성능 관점에서 포크하는 것이 더 나은 옵션입니까, 아니면 더 나쁜 옵션입니까?
답변1
strace를 실행할 때 제가 느낀 점 중 하나는 Futex 사용량이 8~10배 폭발적으로 증가했다는 것입니다. LWP가 이것의 주된 이유가 될 수 있습니까? [...], 하지만 LWP는 동일한 메모리 공간을 공유하므로 Futex 사용량이 폭발적으로 늘어나서는 안 됩니다.
예, LWP를 사용하면 아마도 Futexes의 사용량이 늘어날 것입니다. 이는 실제로 동일한 메모리를 공유하는 서로 다른 스레드의 동기화라는 정확한 경우를 의미하기 때문입니다.
Futexes는 공유 메모리가 있을 때 모든 잠금 작업의 느린 경로에 사용되며 LWP 또는 스레드는 잠금이 잠금 해제되었다는 알림을 받을 때까지 커널에 이를 차단하도록 지시합니다.
빠른 경로는 원자성 작업을 사용합니다(CPU는 카운터를 원자적으로 증가 또는 감소시켜 첫 번째로 잠겼는지 아니면 마지막으로 잠금 해제했는지 감지할 수 있음). 따라서 빠른 경로에서 시스템 호출을 실행할 필요가 없습니다.
잠금 경합이 증가한다는 것은 더 많은 Futex 작업이 발생한다는 것을 의미하며, 이는 시스템 호출 자체로 인해 뿐만 아니라 Futex가 호출될 때 일부 LWP 또는 스레드가 리소스를 기다리며 절전 모드에 있음을 의미하기 때문에 성능에 영향을 미칠 수 있습니다.
glibc의 코드는 멀티스레딩 또는 LWP 사용을 인식하므로 코드에 명시적인 잠금이 없더라도 시스템 라이브러리에 잠금이 있으므로 잠금 경합이 발생할 수 있으며 이로 인해 다음과 같이 프로그램 속도가 느려질 수 있습니다. .
성능이 많이 떨어졌습니다(처리 속도가 30% 느려졌습니다).
메모리를 공유하는 스레드가 많을 때의 또 다른 요인은 대략적인 잠금이 있는 일부 커널 메모리 구조가 있고 잠금 경합도 있을 수 있다는 것입니다.
특히 mmap_sem
더 많은 메모리 영역을 프로세스에 매핑할 때마다 쓰기를 위해 해당 영역을 잠가야 합니다. (특히 malloc()
친구와 함께 더 많은 메모리를 할당가능한이것을 트리거합니다. )
이제 나는 LWP를 예약하고 우선순위를 할당하는 것이 가능하다는 것을 읽었습니다(나도 그것을 시도하지 않았습니다). 이것이 성능에 도움이 됩니까?
어쩌면...말하기 어렵습니다. 벤치마킹해야합니다.
보고 있는 내용이 잠금 경합이고 코드 경로를 통해 일반화(단일 또는 몇 개의 LWP로 지역화되지 않음)된 경우에는 도움이 되지 않을 것입니다.
당신은 그것을 사용할 수 있습니다perf
도구Linux에서 일련의 프로세스 성능을 이해하는 데 도움이 됩니다. 핫스팟을 표시할 수 있으며 커널 핫스팟이 존재하는지 여부도 표시할 수 있습니다.
LWP를 사용하거나 사용하기로 결정할 때 따라야 할 팁이나 모범 사례가 있습니까?
LWP나 스레드의 경우 다수를 사용할 경우 malloc()
커널과 관련된 문제(메모리 맵 확장으로 인해 잠재적인 경합이 발생함 mmap_sem
)와 사용자 공간의 문제(단일 경기장을 사용한다는 의미)가 있기 때문에 구현이 매우 중요해집니다. 스레드(공간을 보존하려면 잠가야 함).
많은 수의 스레드를 사용할 때 성능을 향상시키기 위해 일부 malloc 라이브러리가 작성되었습니다. 예를 들어,tcmalloc또는자말 로크. 이를 채택하는 것은 일반적으로 간단하며(추가 라이브러리에 링크하기만 하면 됨) 실제로 병목 현상이 발생하는 경우 성능이 크게 향상될 수 있습니다. 항상 그렇듯이 벤치마크를 통해 이것이 도움이 되는지 확인하세요.
성능 관점에서 포크하는 것이 더 나은 옵션입니까, 아니면 더 나쁜 옵션입니까?
아마도 더 좋을 것입니다. 말하기는 어렵습니다. 특정 사례에서 더 나은지 확인하려면 벤치마킹해야 합니다.
LWP와 마찬가지로 벤치마크를 통해 실제로 가치가 있는지 확인해야 합니다.
위에서 언급했듯이 LWP 또는 스레드 간에 공유 메모리를 사용하면(또는 동일한 메모리를 공유하는 더 많은 LWP 또는 스레드가 있으면) 잠금 경합 가능성이 높아집니다(직접 명시적인 잠금이 없더라도 glibc와 커널은 이 작업을 수행합니다.) LWP 실제로 속도가 느려질 가능성이 높습니다.
저는 멀티스레드 애플리케이션이 너무 느린 상황을 직접 목격했습니다. 개발자는 40개의 스레드 대신 단일 스레드를 사용하도록 변경했습니다. 응용프로그램 속도가 갑자기 1,000% 빨라졌습니다. 90%의 시간이 잠금 경합에 소비되는 것으로 나타났습니다!
답변2
며칠간의 테스트 끝에 다음과 같은 사실을 발견했습니다.
Futexes는 스레드 간에 메모리 버퍼를 공유하는 데서 발생하며(안타깝게도 피할 수 없음) 스레드는 상당히 높은 빈도로 수학적 모델을 실행합니다. futex는 실행 지연 시간에 직접적인 영향을 주지만 선형적으로는 영향을 미치지 않으며 데이터 빈도가 높을수록 영향이 더 커집니다.
대부분의 데이터 크기를 알고 있으므로 일부 할당에는 메모리 풀이나 이와 유사한 사용을 피할 수 있습니다. 이는 실행 및 CPU 로드에 긍정적인 영향을 미칩니다.
LWP는 상위 PID와 다른 PID를 사용하여 복제합니다. 이는 Linux에서는 문제가 없지만 pThread에서는 작동하지 않습니다. 성능적인 측면에서는 LWP로 인한 성능저하가 일부 있으나 크게 저하되지는 않습니다. 공유 메모리 자원은 더 큰 문제를 야기합니다.
jeMalloc, tcMalloc 및 locklessMalloc을 사용하여 애플리케이션을 구축하는 경우 이러한 라이브러리 중 어느 것도 나에게 경쟁 우위를 제공하지 못했습니다. 코어 수가 4개 이상이면 TcMalloc이 좋고, 캐시가 크면 jeMalloc이 좋습니다. 하지만 제 경우에는 여러 실행 시나리오의 결과가 기준선과 +/- 1% 달랐습니다.
더 많은 메모리 영역을 프로세스에 매핑하면 전체 실행에 큰 차이가 생길 수 있습니다. FillBraden의 말이 맞습니다. 실행이 시작되거나 데이터 흐름으로 인해 데이터 양이 늘어날 때 우리에게 큰 타격을 줍니다. 우리는 메모리 풀을 사용하여 동작을 업그레이드했습니다.
실행 속도도 향상시키는 SCHED_RR을 사용하여 애플리케이션을 실행하는 테스트 시리즈가 포함되어 있습니다. 문제는 우선순위 측면에서 스레드의 점수도 더 높기 때문에 이것이 영향을 미친다는 것입니다. 장점은 하이퍼스레딩 없이도 매우 안정적으로 코어를 실행할 수 있다는 것입니다. 그 이유는 애플리케이션과 모델의 동작 때문입니다. 알 수 없는 이유로 하이퍼스레딩은 상황을 상당히 혼란스럽게 만듭니다.
개별 모델을 분기하면 어떤 스레드가 어떤 모델에 속하는지 식별하는 데 도움이 되지만 실행 속도에는 아무런 이점이 없습니다. 이는 확실히 제대로 작동하지 않는 모델 스레드를 식별하고 수정하기 위한 거래이자 솔루션입니다.