3.同期(4/4):性能
3.6 性能
最後に同期処理の性能比較を行います。 下記プログラムを実行し、ロック/アンロックの数値を出します。引数で与えられた数値の数だけスレッドを起動し、その中でひたすらロックとアンロックを20,000,000回繰り返します。プログラムのボリューム上、エラー処理および引数チェックを一切行っていないので、プログラムの先頭に書かれた実行方法以外では異常終了します。
/*
* gcc lock_test.c -o lock_test -g -W -Wall -lpthread
* time ./lock_test <m|s|r|w> <1...8>
*/ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sys/time.h> #define LOOP_CNT 20000000 #define THR_CNT 8 pthread_mutex_t mutex; pthread_spinlock_t lock; pthread_rwlock_t rw_lock; void * spin_func ( void * arg ); void * mutex_func ( void * arg ); void * read_func ( void * arg ); void * write_func ( void * arg ); int main( int argc, char ** argv ) { ( void )argc; pthread_t id[THR_CNT]; int cnt = atoi( argv[2] ); int i; switch( *argv[1] ) { case 'm': pthread_mutex_init( &mutex, 0 ); for( i = 0; i < cnt; i ++ ) { pthread_create( &id[i], 0, mutex_func, 0 ); } break; case 's': pthread_spin_init( &lock, PTHREAD_PROCESS_PRIVATE ); for( i = 0; i < cnt; i ++ ) { pthread_create( &id[i], 0, spin_func, 0 ); } break; case 'r': pthread_rwlock_init( &rw_lock, 0 ); for( i = 0; i < cnt; i ++ ) { pthread_create( &id[i], 0, read_func, 0 ); } break; case 'w': pthread_rwlock_init( &rw_lock, 0 ); for( i = 0; i < cnt; i ++ ) { pthread_create( &id[i], 0, write_func, 0 ); } break; default: return 1; break; } pthread_exit( 0 ); return 0; } void * spin_func( void * arg ) { ( void )arg; int i; for( i = 0; i < LOOP_CNT; i ++ ) { pthread_spin_lock( &lock ); pthread_spin_unlock( &lock ); } return 0; } void * mutex_func( void * arg ) { ( void )arg; int i; for( i = 0; i < LOOP_CNT; i ++ ) { pthread_mutex_lock( &mutex ); pthread_mutex_unlock( &mutex ); } return 0; } void * read_func( void * arg ) { ( void )arg; int i; for( i = 0; i < LOOP_CNT; i ++ ) { pthread_rwlock_rdlock( &rw_lock ); pthread_rwlock_unlock( &rw_lock ); } return 0; } void * write_func( void * arg ) { ( void )arg; int i; for( i = 0; i < LOOP_CNT; i ++ ) { pthread_rwlock_wrlock( &rw_lock ); pthread_rwlock_unlock( &rw_lock ); } return 0; }
私の環境では以下の通りとなりました。
| 種類 | thread:1 | threads:2 | threads:4 | threads:8 |
| mutex(real) | 0m4.312s | 0m8.258s | 0m16.648s | 0m32.537s |
| mutex(user) | 0m4.289s | 0m8.248s | 0m16.626s | 0m32.483s |
| mutex(sys) | 0m0.004s | 0m0.001s | 0m0.005s | 0m0.014s |
| spin(real) | 0m3.108s | 0m10.067s | 0m33.643s | 2m21.928s |
| spin(user) | 0m3.101s | 0m10.052s | 0m33.582s | 2m21.747s |
| spin(sys) | 0m0.002s | 0m0.004s | 0m0.003s | 0m0.010s |
| read lock(real) | 0m6.659s | 0m13.882s | 0m26.806s | 0m53.334s |
| read lock(user) | 0m6.652s | 0m13.863s | 0m26.762s | 0m53.252s |
| read lock(sys) | 0m0.002s | 0m0.005s | 0m0.004s | 0m0.014s |
| write lock(real) | 0m6.804s | 0m13.724s | 0m44.236s | 2m17.293s |
| write lock(user) | 0m6.778s | 0m13.694s | 0m33.246s | 1m25.993s |
| write lock(sys) | 0m0.001s | 0m0.022s | 0m10.934s | 0m50.971s |
私の環境だけで結論を出す事はできないので、あくまで参考値として見ていただければと思います。
こうしてみると、spinは単一スレッドの場合は最速ですがスレッド数が上がるに連れて遅くなります。スレッド数が上がるにつれCPU使用率がネックになって処理が進まないのでしょう。
ちなみにCPUを複数抱えている環境、例えば4個のCPUを持つ環境では、threads:4までは少なくともmutexよりも高速に動作するはずです。クリティカルセクションが狭く、スレッド数がCPU数と比べて少ない場合はspinの使用が適してます。
特にそのような状況では無い場合ではmutexの使用が良いと思います。
write lockはさすがに性能が良くないです。これはwrite lockを行う際にread/writeを行っているスレッドがいない事を確認している為です。シビアな性能を求められない状況ならば、read/write lockは使いたい機能です。
次回は条件変数と、今までの機能で作った簡単なアプリケーションを紹介します。
