잠금 문은 얼마나 비쌉니까?


111

저는 다중 스레딩과 병렬 처리를 실험 해 왔으며 처리 속도에 대한 기본적인 계산 및 통계 분석을 수행하기 위해 카운터가 필요했습니다. 내 클래스의 동시 사용 문제를 피하기 위해 내 클래스의 개인 변수에 잠금 문을 사용했습니다.

private object mutex = new object();

public void Count(int amount)
{
 lock(mutex)
 {
  done += amount;
 }
}

하지만 궁금한 게 있는데 ... 변수를 잠그는 데 얼마나 비쌉니까? 성능에 부정적인 영향은 무엇입니까?


10
변수를 잠그는 것은 그렇게 비싸지 않습니다. 피하고 싶은 잠긴 변수를 기다리는 것입니다.
Gabe 2011 년

53
다른 경쟁 조건을 추적하는 데 시간을 보내는 것보다 훨씬 저렴합니다 ;-)
BrokenGlass 2011 년

2
글쎄 ... 잠금이 비싸다면 잠금이 더 적게 필요하도록 프로그래밍을 변경하여 피할 수 있습니다. 일종의 동기화를 구현할 수 있습니다.
Kees C. Bakker 2011 년

1
잠금 블록에서 많은 코드를 이동하여 성능이 크게 향상되었습니다 (지금 @Gabe의 주석을 읽은 후). 결론 : 지금부터는 "시간 잠금"과 같이 잠금 블록 안에 변수 액세스 (일반적으로 한 줄) 만 남겨 둘 것입니다. 말이 되나요?
heltonbiker

2
@heltonbiker 물론 의미가 있습니다. 그것은 또한 건축 적 원칙이어야하며, 가능한 한 짧고 간단하며 빠르게 자물쇠를 만들어야합니다. 동기화해야하는 정말로 필요한 데이터 만. 서버 상자에서는 잠금의 하이브리드 특성도 고려해야합니다. 코드에 중요하지 않더라도 경쟁은 잠금의 하이브리드 특성 덕분에 다른 사람이 잠금을 보유한 경우 각 액세스 중에 코어가 회전합니다. 스레드가 일시 중단되기 전에 일정 시간 동안 서버의 다른 서비스에서 일부 CPU 리소스를 효과적으로 삼키고 있습니다.
ipavlu

답변:


86

여기 비용에 관한 기사 가 있습니다. 짧은 대답은 50ns입니다.


39
짧고 더 나은 대답 : 50ns + 다른 스레드가 잠금을 유지하는 경우 대기하는 데 소요 된 시간.
Herman

4
더 많은 스레드가 잠금에 들어오고 나 갈수록 더 많은 비용이 발생합니다. 스레드 수에 따라 비용이 기하 급수적으로 증가합니다
Arsen Zahray 2015 년

16
일부 컨텍스트 : 3Ghz x86에서 두 숫자를 나누는 데는 약 10ns가 걸립니다 (명령을 가져 오거나 디코딩하는 데 걸리는 시간은 포함되지 않음) . (캐시되지 않은) 메모리에서 레지스터로 단일 변수를로드하는 데는 약 40ns가 걸립니다. 이 50ns 그래서 미친 듯이이다 눈부시게 빠른 - 당신이 사용하는 비용에 대해 걱정하지 말아야 lock더 이상 당신이 변수를 사용하는 비용에 대해 걱정하는 것보다.
BlueRaja-Danny Pflughoeft 2015 년

3
또한이 질문을했을 때 그 기사는 오래되었습니다.
Otis

3
부정확 한 것은 말할 것도없고 "비용이 거의 들지 않는"정말 훌륭한 측정 항목입니다. 당신들은 고려하지 않습니다, 그것은 짧고 빠르며, 경쟁이 전혀없는 경우에만 하나의 스레드입니다. 이러한 경우 잠금 장치가 전혀 필요하지 않습니다. 두 번째 문제는 잠금이 잠금이 아니라 하이브리드 잠금입니다. CLR 내부에서 원자 적 작업을 기반으로 잠금을 보유하고 있지 않은 것을 감지하고 이러한 경우에 측정되지 않는 다른 링인 운영 체제 코어에 대한 호출을 피합니다. 테스트. 25ns에서 50ns로 측정되는 것은 잠금이 설정되지 않은 경우 실제로 애플리케이션 레벨 연동 명령 코드입니다.
ipavlu

50

기술적 대답은 이것이 정량화가 불가능하다는 것입니다. 이는 CPU 메모리 후기 입 버퍼의 상태와 프리 페 처가 수집 한 데이터의 양을 버리고 다시 읽어야하는 정도에 따라 크게 달라집니다. 둘 다 매우 비 결정적입니다. 나는 큰 실망을 피하는 봉투 뒤의 근사치로 150 CPU 사이클을 사용합니다.

실용적인 대답은 잠금을 건너 뛸 수 있다고 생각할 때 코드를 디버깅하는 데 소모되는 시간보다 훨씬 저렴하다는 것입니다.

어려운 숫자를 얻으려면 측정해야합니다. Visual Studio에는 확장으로 사용할 수 있는 매끄러운 동시성 분석기가 있습니다.


1
실제로 아니요, 정량화하고 측정 할 수 있습니다. 코드 전체에 이러한 잠금을 작성하고 잠금에 대한 단일 스레드 액세스로 측정 된 신화 인 50ns에 불과하다고 말하는 것만 큼 쉽지 않습니다.
ipavlu

8
"당신이 잠금 건너 뛸 수 있다고 생각합니다" ... 나는 ... 많은 사람들이이 질문을 읽을 때에 있는지의 생각
스눕

30

추가 읽기 :

일반적인 동기화 기본 요소에 관심이 있고 고유 한 시나리오와 스레드 수에 따라 모니터, C # 잠금 문 동작, 속성 및 비용을 파헤 치고있는 몇 가지 기사를 소개하고 싶습니다. 특히 여러 시나리오에서 처리 할 수있는 작업의 양을 이해하기 위해 CPU 낭비 및 처리량 기간에 관심이 있습니다.

https://www.codeproject.com/Articles/1236238/Unified-Concurrency-I-Introduction https://www.codeproject.com/Articles/1237518/Unified-Concurrency-II-benchmarking-methodologies https : // www. codeproject.com/Articles/1242156/Unified-Concurrency-III-cross-benchmarking

원래 답변 :

이런!

THE ANSWER는 본질적으로 잘못된 것으로 여기에 표시된 정답 인 것 같습니다! 답변 작성자에게 정중하게 링크 된 기사를 끝까지 읽어달라고 요청하고 싶습니다.

2003 년 기사 의 저자는 듀얼 코어 머신에서만 측정했으며 첫 번째 측정 사례에서는 단일 스레드로만 잠금측정 했으며 그 결과는 잠금 액세스 당 약 50ns였습니다.

동시 환경의 잠금에 대해서는 아무 것도 말하지 않습니다. 그래서 우리는 기사를 계속 읽어야합니다. 그리고 후반부에 저자는 2 개와 3 개의 스레드로 잠금 시나리오를 측정하고 있었는데, 이는 오늘날 프로세서의 동시성 수준에 가까워졌습니다.

그래서 저자는 듀얼 코어에 두 개의 스레드를 사용하면 잠금 비용이 120ns이고 스레드가 3 개인 경우 180ns가된다고 말합니다. 따라서 동시에 잠금에 액세스하는 스레드 수에 분명히 의존하는 것 같습니다.

따라서 간단합니다. 잠금이 쓸모 없게되는 단일 스레드가 아니면 50ns가 아닙니다.

고려해야 할 또 다른 문제는 평균 시간으로 측정된다는 것입니다 !

반복 시간을 측정하면 대부분이 빠르기 때문에 1ms에서 20ms 사이의 시간이있을 수 있지만 프로세서 시간을 기다리는 스레드는 거의 없으며 밀리 초의 긴 지연도 발생합니다.

이것은 높은 처리량과 낮은 대기 시간이 필요한 모든 종류의 응용 프로그램에 나쁜 소식입니다.

마지막으로 고려해야 할 문제는 잠금 내부에 작업 속도가 느려질 수 있다는 것입니다. 코드 블록이 잠금 내부에서 실행되는 시간이 길수록 경합이 높아지고 지연이 하늘 높이 올라갑니다.

2003 년부터 이미 10 년 이상이 지났습니다. 즉, 완전히 동시에 실행되도록 특별히 설계된 몇 세대의 프로세서이며 잠금으로 인해 성능이 상당히 저하됩니다.


1
명확히하기 위해이 기사에서는 애플리케이션의 스레드 수에 따라 잠금 성능이 저하된다고 말하는 것이 아닙니다. 잠금을두고 경합하는 스레드 수에 따라 성능이 저하됩니다. (이것은 위의 답변에 암시 적이지만 명확하게 명시되어 있지는 않습니다.)
Gooseberry

나는 당신이 이것을 의미한다고 생각한다 : "그래서 그것은 동시 적으로 접근하는 쓰레드의 수에 분명히 의존하는 것처럼 보이며 더 많은 것은 더 나쁘다." 예, 문구가 더 좋을 수 있습니다. 나는 스레드가 동시에 잠금에 액세스하여 경합을 일으키는 "동시 액세스"를 의미했습니다.
ipavlu

20

이것은 성능에 대한 쿼리에 응답하지 않습니다,하지만 난 .NET 프레임 워크가 제공한다는 것을 말할 수있는 Interlocked.Add당신이 당신을 추가 할 수 있습니다 방법 amount당신에 done수동으로 다른 개체에 고정하지 않고 회원.


1
예, 이것이 아마도 가장 좋은 대답 일 것입니다. 그러나 주로 더 짧고 깨끗한 코드 때문입니다. 속도의 차이는 눈에 띄지 않을 것입니다.
Henk Holterman 2011 년

이 답변에 감사드립니다. 나는 자물쇠로 더 많은 일을하고있다. 추가 된 int는 많은 것 중 하나입니다. 제안을 사랑하고 지금부터 사용할 것입니다.
Kees C. Bakker 2011

잠금없는 코드가 잠재적으로 더 빠르더라도 잠금은 훨씬 더 쉽게 올바르게 사용할 수 있습니다. Interlocked.Add 자체는 동기화없이 + =와 동일한 문제가 있습니다.
격납고

10

lock (Monitor.Enter / Exit)는 Waithandle 또는 Mutex와 같은 대안보다 매우 저렴하고 저렴합니다.

그러나 (약간) 느리다면 잘못된 결과를 가진 빠른 프로그램을 원하십니까?


5
하하 ... 저는 빠른 프로그램과 좋은 결과를 위해갔습니다.
Kees C. Bakker 2011 년

@ 헹크 - holterman 명령문 여러 문제가 있습니다 : 먼저 이 질문에 명확하게 가리 켰을 때, 전반적인 성능에 잠금의 영향의 낮은 이해 만 단일 스레드 환경에 적용이 50ns에 대한 신화를 알리는 사람도있다. 두 번째로 귀하의 진술은 여기에 있으며 그동안 프로세서가 코어에서 성장하지만 코어의 속도는 그다지 많지 않습니다. ** Thrid ** 애플리케이션은 시간이 지남에 따라 더 복잡해질뿐입니다. 많은 코어의 환경에서 잠금 및 수 증가, 2,4,8,10,20,16,32
ipavlu

나의 일반적인 접근 방식은 가능한 한 적은 상호 작용으로 느슨하게 결합 된 방식으로 동기화를 구축하는 것입니다. 잠금없는 데이터 구조로 매우 빠르게 진행됩니다. 개발을 단순화하기 위해 스핀 락 주변의 코드 래퍼를 만들었습니다. TPL에 특별한 동시 컬렉션이있는 경우에도 목록, 배열, 사전 및 대기열에 대한 스핀 잠금 컬렉션을 개발했습니다. 약간의 제어가 필요하고 일부 코드에서 실행되는 경우도 있습니다. 스핀 록. TPL 컬렉션이 할 수없는 여러 시나리오를 해결할 수 있으며 성능 / 처리량을 크게 향상시킬 수 있습니다.
ipavlu 2015

7

잠금 장치가없는 대안에 비해 타이트 루프의 잠금 비용은 엄청납니다. 여러 번 반복 할 수 있고 잠금보다 더 효율적입니다. 이것이 잠금 해제 대기열이 매우 효율적인 이유입니다.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LockPerformanceConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            const int LoopCount = (int) (100 * 1e6);
            int counter = 0;

            for (int repetition = 0; repetition < 5; repetition++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    lock (stopwatch)
                        counter = i;
                stopwatch.Stop();
                Console.WriteLine("With lock: {0}", stopwatch.ElapsedMilliseconds);

                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    counter = i;
                stopwatch.Stop();
                Console.WriteLine("Without lock: {0}", stopwatch.ElapsedMilliseconds);
            }

            Console.ReadKey();
        }
    }
}

산출:

With lock: 2013
Without lock: 211
With lock: 2002
Without lock: 210
With lock: 1989
Without lock: 210
With lock: 1987
Without lock: 207
With lock: 1988
Without lock: 208

4
단일 변수 할당과 잠금을 제외하고는 루프가 실제로 아무 작업도하지 않기 때문에 이것은 나쁜 예일 수 있습니다. 또한 잠금 당 20ns는 그렇게 나쁘지 않습니다.
Zar Shardan

5

"비용"을 정의하는 몇 가지 방법이 있습니다. 잠금을 얻고 해제하는 실제 오버 헤드가 있습니다. Jake가 쓴 것처럼이 작업이 수백만 번 수행되지 않는 한 무시할 수 있습니다.

더 관련성이있는 것은 이것이 실행 흐름에 미치는 영향입니다. 이 코드는 한 번에 하나의 스레드에서만 입력 할 수 있습니다. 이 작업을 정기적으로 수행하는 5 개의 스레드가있는 경우 그중 4 개는 잠금이 해제 될 때까지 기다린 다음 해당 잠금이 해제 된 후 해당 코드를 입력하도록 예약 된 첫 번째 스레드가됩니다. 따라서 알고리즘이 크게 저하 될 것입니다. 얼마나 많이 알고리즘과 연산이 호출 되는가에 따라 달라집니다. 경쟁 조건을 도입하지 않고는 실제로 피할 수는 없지만 잠긴 코드에 대한 호출 수를 최소화하여 개선 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.