Малко странна ситуация.
Написах си нещо като много рудиментарен интерпретаторен език за генериране на низове по определни правила. Идеята е свързана с password cracking-а, където някои алгоритми са прекалено изчислително сложни, за да можем да си позволяваме прости атаки от сорта на брутфорс, а от друга страна речниковите атаки са неефективни, понеже изискват прекалено големи речници, за да бъдат ефективни. Идеята е че ако можем да приложим прости трансформации върху някакви низове, можем да генерираме кандидат-пароли, които следват някакви pattern-и. Примерно имаме малък речник от английски думи, искаме да пробваме всяка комбинация от думи от този речник с числата от 0 до 99, като допълнително генерираме всички възможни комбинации където която и да е буква от паролата може да е главна или малка, това на моя "език" става по следния начин:
begin
must add dictionary english.txt
must add set 1:2:num
may togglecase
end
Както и да е, досега доста ненужни подробности. Парсъра на езика е проста работа, защото синтаксиса на езика е прост. Всичко се реализира лесно с function pointer-и като в общи линии има едно множество от трансформации, за които си има съответната функция и парсъра я вика. Това е вече направено и работи

Сега следва забавния момент. Аз имам четириядрен процесор и не мога да го утилизирам като хората. В зависимост от сложността на правилата които парсвам, мога да генерирам до 10-15 милиона кандидата в секунда. Това е напълно достатъчно, за да захраня GPU-тата с кандидати за някои "бавни" алгоритми като например този ползван от WPA-PSK. И напълно недостатъчно, ако искам да ги захраня с кандидати за "бързи" алгоритми, като например SHA1.
Съвсем очевидно следва да тредна парсъра, така че да се изпълнява върху ядрата паралелно. Паралелизацията да речем не е сложна задача, не е сложно да се разбие на независими части, не е embarassingly parallel, но не е далеч от това. Обаче миграцията ми разказа играта. Първата версия беше producer-consumer с определена бройка producer-и и consumer-и, синхронизацията разказа играта на производителността до степен до която single-threaded кода работеше по-добре. Просто прекалено много мутекси и condvar-и не са здравословни, а иначе не работи както трябва.
Втората идея - consumer-ите да са едновременно и producer-и и да приключим с тъпата синхронизация беше логичното следствие. Обаче това означава да пренапиша много брутално целия парсър, да редефинирам функциите да приемат като параметър нещо като thread id, което не е точно thread id-то на нишката (демек не съответства на pthread_self). Това ми строши часове безумни усилия. Да не говорим, че за да направя парсъра thread-safe и това да върви бързо, трябваше да променям структури от данни, захабявайки повече памет, защото силно държа да останат като глобални променливи, обаче всяка нишка да пипа само в нейното си (което от performance гледна точка е най-бързо, стига да нямаме false sharing).
Обаче тези упражнения ми се виждат доста тегави. Та чудно ми е в такъв случай как трябва да се постъпи най-добре?
* Това което аз направих. Моят случай не е толкова сложен и за няколко часа кода стана многонишков, нервите и усилията бяха доста обаче, дори за 2000 реда C код.
* Да ползваме "стандартизирания" POSIX-ки подход към Thread Local Storage-а, с pthread_create_key() и компания. Признавам, че не пробвах. Причината е че преди време си бях играл и бях установил че е бавно поради някаква причина, но може да съм грешал тогава. Като цяло вероятно това е правилния подход, макар че изисква също толкова усилия.
* gcc има някакъв extension, където си декларираш променливата с __thread и тя автоматично се алокира в TLS-а. Това изглежда най-просто и лесно. Обаче ме притеснява че кода няма да е много portable. Някой имал ли е вземане-даване с това?
* Мина ми една грозна мисъл да си реша половината проблеми с препроцесорен макрос. Обаче това ми се вижда дооооооста извратена идея.
Та това е. Някой имал ли е подобен проблем и как го е разрешил, много ми е интересно...