совет по memory model
От: sokel Россия  
Дата: 06.05.15 21:45
Оценка:
Коллеги, посоветуйте...
Хочу атомарный rwlock, но не очень понимаю, что и как должно быть в плане memory order. И да, я в курсе голодных писателей в столь наивной реализации.
Хочется что-то вроде этого:
class atomic_shared_mutex 
{
public:
    atomic_shared_mutex()
        : mtx_(0)
    {}
    void lock() {
        // writer пишет в состояние -1
        int state = mtx_.load();
        while(state != 0 || !mtx_.compare_exchange_strong(state, -1)) {
            std::this_thread::yield();
            state = mtx_.load();
        }
    }
    void unlock() {
        mtx_.store(0);
    }
    void lock_shared() {
        // reader просто увеличивает счётчик собратьев
        int state = mtx_.load();
        for(;;) {
            if(state == -1) {
                std::this_thread::yield();
                state = mtx_.load();
            } else if(mtx_.compare_exchange_strong(state, state+1)) {
                break;
            }
        }
    }
    void unlock_shared() {
        --mtx_;
    }
private:
    std::atomic<int> mtx_;
};
Re: совет по memory model
От: Patalog Россия  
Дата: 07.05.15 03:25
Оценка: -1
Здравствуйте, sokel, Вы писали:

S>Коллеги, посоветуйте...


Если еще не, то советую почитать тов. remark, в частности
Что такое модель памяти? И с чем её едят?
Почетный кавалер ордена Совка.
Re[2]: совет по memory model
От: sokel Россия  
Дата: 07.05.15 07:42
Оценка:
Здравствуйте, Patalog, Вы писали:

P>Если еще не, то советую почитать тов. remark, в частности

P>Что такое модель памяти? И с чем её едят?

Да, спасибо, читал.
В моём представлении должно быть как то так: acquire/release на входе/выходе + дополнительное relaxed чтение при блокировке читателем.
class atomic_shared_mutex {
public:
    atomic_shared_mutex() : mtx_(0) {}
    void lock_shared() {
        // изначально читаем в relaxed, барьер будет далее, в CAS операции
        int state = mtx_.load(std::memory_order_relaxed);
        for(;;) {
            if(state < 0) {
                // занят писателем - прерываем поток, при возврате перечитываем состояние
                std::this_thread::yield();
                state = mtx_.load(std::memory_order_relaxed);
            }
            // не занят, увеличиваем счётчик читателей
            else if(mtx_.compare_exchange_weak(state, state+1, std::memory_order_acquire))
                break;
        }
    }
    void unlock_shared() {
        mtx_.fetch_sub(1, std::memory_order_release);
    }
    void lock() {
        for(;;) {
            int state = 0;
            if(mtx_.compare_exchange_weak(state, -1, std::memory_order_acquire))
                break;
            // попытка не удалась, использовали weak CAS, отдадим управление 
            // только если мьютекс действительно занят
            if(state != 0)
                std::this_thread::yield();
        }
    }
    void unlock() {
        mtx_.store(0, std::memory_order_release);
    }
private:
    std::atomic<int> mtx_;
};
Re[3]: совет по memory model
От: sokel Россия  
Дата: 07.05.15 08:00
Оценка:
Здравствуйте, sokel, Вы писали:

S> дополнительное relaxed чтение при блокировке читателем.


Хотя на кой чёрт чтение, можно же с оптимизмом предположить, что читатель первым захватывает блокировку.
Тогда достаточно одного CAS:
    void lock_shared() {
        int state = 0;
        for(;;) {
            if(mtx_.compare_exchange_weak(state, state+1, std::memory_order_acquire))
                break;
            if(state < 0) {
                // мьютекс занят писателем - прерываем поток
                // при возврате опять попытаемся быть первыми
                state = 0;
                std::this_thread::yield();
            }
        }
    }
Re: совет по memory model
От: uzhas Ниоткуда  
Дата: 07.05.15 09:12
Оценка:
Здравствуйте, sokel, Вы писали:

S>Хочу атомарный rwlock, но не очень понимаю, что и как должно быть в плане memory order.


я бы назвал это не "атомарный", а spin rw lock (yield может привнести задержку, зависит от твоих задач)
memory order нужен для контроля над реордерингом чтения\записи в разные переменные. у тебя в коде одна переменная, так что реордеринг не повлияет на твой код никак, но может косвенно повлиять на код снаружи имхо
я бы посоветовал не увлекаться реордерингом и заюзал seq_cst/
очень часто примитивы синхронизации сами по себе являются еще и барьерами (каждый вызов метода имеется в виду), это распространенная практика
Re: совет по memory model
От: ELazin http://rsdn.ru/forum/prj/6225353.1
Автор: ELazin
Дата: 26.10.15
Дата: 07.05.15 09:52
Оценка: -1
Дешево и сердито (и псевдокодисто):

struct RWSpinLock {

  SpinLock locks[NLOCKS] = {0};
  
  void readlock() {
    auto id = get_thread_id();
    locks[id%NLOCKS].lock();
  }

  void readunlock() {
    auto id = get_thread_id();
    locks[id%NLOCKS].unlock();
  }

  void writelock() {
    for(int i = NLOCKS; i--> 0;) {
      locks[i].lock();
    }
  }

  void writeunock() {
    for(int i = 0; i < NLOCKS; i++) {
      locks[i].unlock();
    }
  }
};


write starvation отсутствует, работает шустро
Re[4]: совет по memory model
От: uzhas Ниоткуда  
Дата: 07.05.15 11:03
Оценка:
Здравствуйте, sokel, Вы писали:

S>Тогда достаточно одного CAS:

S> void lock_shared() {


мне кажется, во всех вариантах lock_shared имеет логическую ошибку
я бы начал с этого (ордерингом не заморачиваюсь по причинам, описанным выше)

class atomic_shared_mutex {
public:
    void lock_shared() {
        for(;;) {
            int state = mtx_;
            if(state < 0) {
                // занят писателем - прерываем поток, при возврате перечитываем состояние
                std::this_thread::yield();
            }
            // не занят, увеличиваем счётчик читателей
            else if(mtx_.compare_exchange_strong(state, state+1))
                break;
        }
    }

    void unlock_shared() {
        --mtx_;
    }

    void lock() {
        for(;;) {
            int state = 0;
            if(mtx_.compare_exchange_strong(state, -1))
                break;
            std::this_thread::yield();
        }
    }

    void unlock() {
        mtx_ = 0;
    }

private:
    std::atomic<int> mtx_;
};
Re[2]: совет по memory model
От: ELazin http://rsdn.ru/forum/prj/6225353.1
Автор: ELazin
Дата: 26.10.15
Дата: 07.05.15 11:52
Оценка:
username uzhas, есть какие-то конкретные доводы против этой схемы?
Re[5]: совет по memory model
От: sokel Россия  
Дата: 07.05.15 12:51
Оценка:
Здравствуйте, uzhas, Вы писали:

U>мне кажется, во всех вариантах lock_shared имеет логическую ошибку

А в чём конкретно ошибка?

U>я бы начал с этого (ордерингом не заморачиваюсь по причинам, описанным выше)

Ну я как бы насчёт ордеринга только и спрашивал изначально

Погонял тесты, вроде нормально работает.
Тесты в попугаях, что то вроде N/M потоков читателей/писателей в цикле толкаются на доступе к шаред данным.
Данные — массив из 100 интов, писатель делает всем инкремент, читатель проверяет что они все равны.

Интересно ещё вот что — думал под Windows std::mutex будет критической секцией, ан нет... и тормозней он чего то (vs 2013).

linux:
pthread_rwlock (size: 56)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :    1294071 :     571664 :    1865735 : 2.26369
 2/1 :    3962185 :       9033 :    3971218 : 438.634
 3/1 :    5513209 :       7775 :    5520984 : 709.094
 1/2 :     320766 :    2246520 :    2567286 : 0.142784
 2/2 :    3457416 :     253457 :    3710873 : 13.641
 3/2 :    5372590 :      95348 :    5467938 : 56.3472
spin_rwlock (size: 4)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :    1514716 :    1639941 :    3154657 : 0.923641
 2/1 :    3592230 :     163991 :    3756221 : 21.905
 3/1 :    5315448 :      36577 :    5352025 : 145.322
 1/2 :    1113476 :    2363952 :    3477428 : 0.471023
 2/2 :    2750755 :     688580 :    3439335 : 3.99482
 3/2 :    4159673 :     268165 :    4427838 : 15.5116
pthread_mutex (size: 40)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :    1691200 :     847683 :    2538883 : 1.99509
 2/1 :    1311589 :     392075 :    1703664 : 3.34525
 3/1 :    1076801 :     222661 :    1299462 : 4.83606
 1/2 :    1106446 :    1109263 :    2215709 : 0.99746
 2/2 :     991612 :     507319 :    1498931 : 1.95461
 3/2 :     983016 :     399820 :    1382836 : 2.45865
std::mutex (size: 40)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :    1741647 :     612609 :    2354256 : 2.843
 2/1 :    1358207 :     252725 :    1610932 : 5.37425
 3/1 :    1084528 :     156240 :    1240768 : 6.94142
 1/2 :    1223586 :     806134 :    2029720 : 1.51784
 2/2 :     964620 :     613355 :    1577975 : 1.57269
 3/2 :     912177 :     454899 :    1367076 : 2.00523


WIN64:
SRWLock (size: 8)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :    1562930 :     196705 :    1759635 : 7.94555
 2/1 :    2861483 :      70337 :    2931820 : 40.6825
 3/1 :    3659780 :       8578 :    3668358 : 426.647
 1/2 :    1048075 :    5424068 :    6472143 : 0.193227
 2/2 :    2511491 :     517188 :    3028679 : 4.85605
 3/2 :    2806304 :     466099 :    3272403 : 6.02083
spin_rwlock (size: 4)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :    1448400 :    1029151 :    2477551 : 1.40737
 2/1 :    2706854 :     424378 :    3131232 : 6.3784
 3/1 :    3748228 :      48171 :    3796399 : 77.8109
 1/2 :    1009395 :    4375400 :    5384795 : 0.230698
 2/2 :    2461366 :     419266 :    2880632 : 5.87065
 3/2 :    3787073 :      49243 :    3836316 : 76.9058
CRITICAL_SECTION (size: 40)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :     732394 :    4232779 :    4965173 : 0.173029
 2/1 :    1057074 :    2805175 :    3862249 : 0.37683
 3/1 :    1196694 :    1469344 :    2666038 : 0.814441
 1/2 :     783507 :    4649181 :    5432688 : 0.168526
 2/2 :    1042375 :    2807732 :    3850107 : 0.371252
 3/2 :     942083 :    2945659 :    3887742 : 0.319821
std::mutex (size: 8)
 R/W :      reads :     writes :      total : R/W factor
 1/1 :     915780 :     919961 :    1835741 : 0.995455
 2/1 :     509200 :     254613 :     763813 : 1.9999
 3/1 :     583516 :     195870 :     779386 : 2.9791
 1/2 :     381415 :     762516 :    1143931 : 0.500206
 2/2 :     648031 :     646559 :    1294590 : 1.00228
 3/2 :     130631 :      88001 :     218632 : 1.48443
Re[2]: совет по memory model
От: sokel Россия  
Дата: 07.05.15 12:57
Оценка:
Здравствуйте, ELazin, Вы писали:

EL>Дешево и сердито (и псевдокодисто):

Совсем не дёшево по размеру.
Re[3]: совет по memory model
От: ELazin http://rsdn.ru/forum/prj/6225353.1
Автор: ELazin
Дата: 26.10.15
Дата: 07.05.15 13:41
Оценка:
S>Совсем не дёшево по размеру.

Зато отсутствует contention при блокировке на чтение.
Re[6]: совет по memory model
От: uzhas Ниоткуда  
Дата: 07.05.15 13:52
Оценка:
Здравствуйте, sokel, Вы писали:

S>А в чём конкретно ошибка?

мне показалось, что если проинкрементить state не получилось, то забыли перечитать актуальное состояние. я ошибся, это делает метод compare_exchange_strong

S>Интересно ещё вот что — думал под Windows std::mutex будет критической секцией, ан нет... и тормозней он чего то (vs 2013).

могу предположить, что это связано с нерекурсивностью std::mutex (доп пляски вокруг критической секции в кишках). попробуй recursive_mutex на винде
Re[7]: совет по memory model
От: sokel Россия  
Дата: 07.05.15 21:37
Оценка:
Здравствуйте, uzhas, Вы писали:

U>могу предположить, что это связано с нерекурсивностью std::mutex (доп пляски вокруг критической секции в кишках).

Да, похоже на то. Ещё и pimpl.

U>попробуй recursive_mutex на винде

Да пока меня и my::mutex на CRITICAL_SECTION устраивает. С posix вот тоже не везде гладко. Например для HP-UX полезно мьютекс подспинлочить немного, через pthread_mutexattr_setspin_np. Но в std mutex это не учитывается.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.