Skip to content

Instantly share code, notes, and snippets.

@leon-joel
Last active June 1, 2020 16:17
Show Gist options
  • Save leon-joel/7a92c72eafe9d0e28402a8b0809f1151 to your computer and use it in GitHub Desktop.
Save leon-joel/7a92c72eafe9d0e28402a8b0809f1151 to your computer and use it in GitHub Desktop.
[C#] 様々なロック方法のパフォーマンス検証
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
//
// ★元ネタは @tadokoro さんのQiita記事です!感謝!★
// https://qiita.com/tadokoro/items/28b3623a5ec58517d431
//
// 上のものに ReaderWriterLockSlim についての実験を追加しました。
//
// <考察>
// ・データ量が少ない場合は lockなし より Interlocked や lock の方が速かったりもする。
// ・こういう単純な処理だとマルチスレッド化する意味がないこともある
// ・64ビットと32ビットには「測定ごとの揺れ」以上の大きな差は無さそう ※あくまでもロックに関しては。
// ■実験環境
// ・4core8thread (Intel(R) Xeon(R) CPU E5-1620 v4 @ 3.50GHz) mem:32GB
// ・Windows10 x64 Pro 1809
// ・Target Framework: .NET Framework 4.6.1
// ・Release / AnyCPU
// ※「32ビットを選ぶ」オプション On/Off両方で実験
// ★1スレッド【データ量 最も多め】
//concurrency 1
//count 4,000,000 : 64bit(32ビットを選ぶOFF)
//no-lock : 117,647,000 / sec ( 34 msec) | Valid
//interlocked : 72,727,000 / sec ( 55 msec) | Valid
//lock : 43,956,000 / sec ( 91 msec) | Valid
//ReaderWriterLockSlim : 16,949,000 / sec ( 236 msec) | Valid
//semaphoreSlim : 7,532,000 / sec ( 531 msec) | Valid
// ★2スレッド【データ量多め】
//concurrency 2
//count 2,000,000 : 64bit(32ビットを選ぶOFF)
//no-lock : 38,461,538 / sec ( 52 msec) | Broken
//interlocked : 22,222,222 / sec ( 90 msec) | Valid
//lock : 19,047,619 / sec ( 105 msec) | Valid
//ReaderWriterLockSlim : 9,803,921 / sec ( 204 msec) | Valid
//semaphoreSlim : 3,968,253 / sec ( 504 msec) | Valid
// ★4スレッド【データ量多め】
//concurrency 4
//count 2,000,000 : 64bit(32ビットを選ぶOFF)
//no-lock : 13,245,033 / sec ( 151 msec) | Broken
//interlocked : 7,905,138 / sec ( 253 msec) | Valid
//lock : 8,368,200 / sec ( 239 msec) | Valid
//ReaderWriterLockSlim : 3,401,360 / sec ( 588 msec) | Valid
//semaphoreSlim : 1,483,679 / sec ( 1,348 msec) | Valid
// ★4スレッド
//concurrency 4
//count 100,000 : 64bit(32ビットを選ぶOFF) | 32ビットを選ぶON
//no-lock : 3,030,303 / sec ( 33 msec) | 3,448,275 / sec ( 29 msec) | Broken
//interlocked : 4,761,904 / sec ( 21 msec) | 5,000,000 / sec ( 20 msec) | Valid
//lock : 4,347,826 / sec ( 23 msec) | 6,250,000 / sec ( 16 msec) | Valid
//ReaderWriterLockSlim : 2,380,952 / sec ( 42 msec) | 2,857,142 / sec ( 35 msec) | Valid
//ReaderWriterLock : 40,128 / sec ( 2,492 msec) | 39,215 / sec ( 2,550 msec) | Valid
//semaphoreSlim : 961,538 / sec ( 104 msec) | 934,579 / sec ( 107 msec) | Valid
//semaphore : 38,789 / sec ( 2,578 msec) | 36,258 / sec ( 2,758 msec) | Valid
//mutex : 38,535 / sec ( 2,595 msec) | 37,807 / sec ( 2,645 msec) | Valid
// ★8スレッドは4スレッドの半分くらいのパフォーマンスしかでていない
//concurrency 8
//count 100,000 : 64bit(32ビットを選ぶOFF) | 32ビットを選ぶON
//no-lock : 2,380,952 / sec ( 42 msec) | 2,040,816 / sec ( 49 msec) | Broken
//interlocked : 2,631,578 / sec ( 38 msec) | 2,777,777 / sec ( 36 msec) | Valid
//lock : 2,083,333 / sec ( 48 msec) | 1,538,461 / sec ( 65 msec) | Valid
//ReaderWriterLockSlim : 1,315,789 / sec ( 76 msec) | 1,515,151 / sec ( 66 msec) | Valid
//ReaderWriterLock : 20,020 / sec ( 4,995 msec) | 19,338 / sec ( 5,171 msec) | Valid
//semaphoreSlim : 310,559 / sec ( 322 msec) | 328,947 / sec ( 304 msec) | Valid
//semaphore : 19,459 / sec ( 5,139 msec) | 18,882 / sec ( 5,296 msec) | Valid
//mutex : 19,230 / sec ( 5,200 msec) | 18,501 / sec ( 5,405 msec) | Valid
// ★32スレッドはものすごく遅い
//concurrency 32
//count 100,000 : 64bit(32ビットを選ぶOFF) | 32ビットを選ぶON
//no-lock : 1,063,829 / sec ( 94 msec) | 1,449,275 / sec ( 69 msec) | Broken
//interlocked : 1,030,927 / sec ( 97 msec) | 1,190,476 / sec ( 84 msec) | Valid
//lock : 666,666 / sec ( 150 msec) | 518,134 / sec ( 193 msec) | Valid
//ReaderWriterLockSlim : 337,837 / sec ( 296 msec) | 350,877 / sec ( 285 msec) | Valid
//ReaderWriterLock : 4,977 / sec ( 20,089 msec) | 4,831 / sec ( 20,696 msec) | Valid
//semaphoreSlim : 12,901 / sec ( 7,751 msec) | 11,043 / sec ( 9,055 msec) | Valid
//semaphore : 4,784 / sec ( 20,903 msec) | 4,700 / sec ( 21,273 msec) | Valid
//mutex : 4,775 / sec ( 20,939 msec) | 4,573 / sec ( 21,863 msec) | Valid
namespace LockBenchmarkConsole
{
class Program
{
public static void Main()
{
new BenchmarkLock().Run();
}
}
class BenchmarkLock
{
const int concurrency = 1;
const int count = 1000 * 1000 * 4;
int index;
public void Run()
{
ThreadPool.SetMinThreads(concurrency, concurrency);
ThreadPool.SetMaxThreads(concurrency, concurrency);
Console.WriteLine(string.Format("concurrency {0:n0}", concurrency));
Console.WriteLine(string.Format("count {0:n0}", count));
bench("no-lock ", broken);
bench("interlocked ", interlocked);
bench("lock ", lock_);
bench("ReaderWriterLockSlim ", readerwriterlockSlim);
//bench("ReaderWriterLock ", readerwriterlock);
bench("semaphoreSlim ", semaphoreSlim);
//bench("semaphore ", semaphore);
//bench("mutex ", mutex);
}
void bench(string name, Action<int[]> f)
{
var s = Stopwatch.StartNew();
index = -1;
var length = count * concurrency;
var xs = new int[length];
var tasks = Enumerable.Range(1, concurrency).Select(async _ => {
await Task.Delay(1).ConfigureAwait(false);
for (int i = 0; i < count; ++i) {
f(xs);
}
}).ToArray();
Task.WaitAll(tasks);
Console.WriteLine("{0}: {1,10:n0} / sec ({2,7:n0} msec) | {3}",
name,
(count) / s.ElapsedMilliseconds * 1000,
s.ElapsedMilliseconds,
(xs.All(x => x == 1) && length == (1 + index)) ? "Valid" : "Broken");
}
void broken(int[] xs)
{
var n = ++index;
xs[n]++;
}
void interlocked(int[] xs)
{
var n = Interlocked.Increment(ref index);
xs[n]++;
}
object lockObject = new object();
void lock_(int[] xs)
{
lock (lockObject) {
var n = ++index;
xs[n]++;
}
}
ReaderWriterLock rwl = new ReaderWriterLock();
void readerwriterlock(int[] xs)
{
rwl.AcquireWriterLock(Timeout.Infinite);
try {
var n = ++index;
xs[n]++;
} finally {
rwl.ReleaseLock();
}
}
ReaderWriterLockSlim rwls = new ReaderWriterLockSlim();
void readerwriterlockSlim(int[] xs)
{
rwls.EnterWriteLock();
try {
var n = ++index;
xs[n]++;
} finally {
rwls.ExitWriteLock();
}
}
Semaphore sem = new Semaphore(1, 1);
void semaphore(int[] xs)
{
sem.WaitOne();
try {
var n = ++index;
xs[n]++;
} finally {
sem.Release();
}
}
SemaphoreSlim semSlim = new SemaphoreSlim(1, 1);
void semaphoreSlim(int[] xs)
{
semSlim.Wait();
try {
var n = ++index;
xs[n]++;
} finally {
semSlim.Release();
}
}
Mutex mut = new Mutex();
void mutex(int[] xs)
{
mut.WaitOne();
try {
var n = ++index;
xs[n]++;
} finally {
mut.ReleaseMutex();
}
}
}
}
@leon-joel
Copy link
Author

leon-joel commented Jun 26, 2019

元ネタは @tadokoro さんのQiita記事です!感謝!
https://qiita.com/tadokoro/items/28b3623a5ec58517d431

@tadokoro さんのものに ReaderWriterLockSlim についての実験を追加しました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment