Это так, не вопрос.. а просто "Крик души…" как не надо делать!
Может кому пригодиться
Написал небольшой кусочек кода, что то типа простейшего менеджера памяти.Многие могут возразить "зачем изобретать велосипед и писать свой менеджер памяти?"
Отвечаю: необходимо было выделить достаточный объем непрерывной
физической памяти в которую складировать некие события, с той целью что если во время сбора данных от устройства, произойдет BSOD – то после того как система сдампит память на диск я смогу найти свой непрерывно выделенный буфер в файле и анализировать его, увидеть последние события которые обрабатывало устройство в тот момент. Ну в общем все по образу и подобию работы утилитки DebugView.
Так вот. Алгоритм довольно простой. Во время инициализации выделяю кусок физической памяти и в нем по мере необходимости выделяю более мелкие блочки для сохранение потока данных! Т.к запросы на выделение памяти могут прийти в произвольный момент и от произвольного контекста,
необходимо было добавить синхронизацию! Т.к вся эта обработка происходит в Kernel-mode то на ум сразу пришло использовать SpinLock!
!!!! И вот тут сыграло мое стремление заоптимизить кусочек кода.
Зачем использовать SpinLock и синхронизироваться посредством хард-процессорной синхронизации, когда можно использовать софтварный Mutex, что будет более приемлемо и на многопроцессорной машине не будет переводить проц в бесполезный цикл, если в этот момент SpinLock был захвачен другим процом.
Вот я и заложил БОМБУ замедленного действия в свой код. Mutex он конечно хороший объект синхронизации, но для user-mode. В kernel-mode есть некие ограничения! И эти ограничения вылетели у меня в тот момент из головы. Заглянуть в MSDN тоже не сообразил. Написал код – запустил. Все отлично работает…
работает…
работает… пару минут… и…
И в произвольный момент времени система умирает… Вернее не вся система в целом, а лишь потоки user-mode перестают получать возможность выполняться и вытесняются потоками kernel-mode.
Провозившись около часа и закомментировав в коде все что только возможно, оставил лишь следующий кусочек кода:
ExAcquireFastMutex(&g_DeviceExtension->g_drMutex);
Count++;
ExReleaseFastMutex(&g_DeviceExtension->g_drMutex);
Который как видно он ничего не делает, кроме как безопасно инкрементирует переменную!
и тут пришло озарение… Оптимизация Блин.. хотел сделать лучше… а вышло…
Дело оказалось в том, что система опрашивала устройство в тот момент когда захватывать Mutex нельзя ни при каких условиях IRQL поднимался до DISPATCH_LEVEL, это в итоге приводило к тому, что user-mode потоки не могли получить шанс на выполнение, в виду того, что скорей всего не была завершена синхронная операция выполняющаяся
на уровне между PASSIVE_LEVEL < … < DISPATCH_LEVEL.
Вот… переписав код и поменяв Mutex на Spinlock все работает прекрасно!
Так что иногда можно даже в логически правильном коде сделать грубейшую ошибку, выловить которую будет очень трудно!
Всем удачи!
20 февраля 2009 в 14:04
Эт про 5% и пряморуких программистов я не знаю Конечно, когда надо делать уйму вычислений, то ООП может навредить. Но я не говорил про случаи, когда каждый такт процессора важен – тогда надо на асме писать Я просто говорю, что всегда лучше не полагаться на какие-то слухи о тормозах, а самому проверить и выяснить, есть тормоза или нет, то есть то самое профилирование.
А для kernel и вправду интересно писать Но это кому как, конечно.
20 февраля 2009 в 11:02
> встречаем 5% падения производительности у самых
> пряморуких программистов.
Тут кстати очень поможет профилировщик.. покажет узкие места, проанализировав которые можно будет переписать кусочки кода которые понижают производительность!
> А вообще написание чего угодно для кернел мода
> виндоус – это лучше сразу написать новую систему и не мучаться.
Как по мне намного интересней писать для Kernel-mode, чем для user-mode:
ответственности больше…и код в режиме ядра выглядит как то более благородней.. структурирований что-ли..
19 февраля 2009 в 22:00
Тысячные доли общего времени исполнения мы получаем, если ООП отвечает за всю железную дорогу Украины. А когда ООП начинают использовать для виртуальных объектов, а особенно для низкоуровневых, а особенно не зная сути применения ООП, именно тогда и получаются висячий и нечитабельный софт.
Запомните – ООП применяется если объект /видно/. Если это чашки/тарелки/яблоки. Операций над ними производится мало, а внутри функциональности много. А уже используя классы для полиномов, над которыми надо оперировать часто, или над длинными числами, сразу встречаем 5% падения производительности у самых пряморуких программистов.
19 февраля 2009 в 21:00
Да, я наконец понял, что вы имеете в виду. Спасибо.
А собственно про ООП: вы указали на обычные его недостатки. Но создание/удаление объекта – это ведь не какие-то особенные операции. А их всегда боятся и говорят, что на них тратится время. Конечно, затраты есть. Но рекомендую: один раз подсчитать (замерить), сколько тысячных долей процента времени выполнения программа проводит в операциях создания/удаления, и навсегда забыть об этих страхах
Конечно, надо правильно проектировать. Если вы будете передавать все объекты по значению, и конструктор копирования будет делать уйму операций, то это будут большие тормоза. Но это уже не дефект ООП, а дефект в голове проектировщика.
Но что-то я ушел от темы
19 февраля 2009 в 20:01
При использовании мьютекса приостановиться поток (если мьютекс захвачен) .. переключиться контекст и проц (в многопроцессорной системе) будет выполнять другую полезную работу… если будет спин-лок – то проц будет тупо ожидать освобождения… тут термин "Оптимизация" имелось в виду – дружественного использования микропроцессоров, а не скорости выполнения….
А вот использование ООП… идея конечно классная.. но как по мне.. будет расходоваться время на создание объекта – вызов конструктора и разрушение – вызов диструктора… (хотя если объект выделяется на стеке.. то значительно быстрее), вместо обычного вызова функций Захвата и Освобождения объекта синхронизации.. но это чисто мое мнение
19 февраля 2009 в 20:00
2 #6: Какое-то разногласие у нас По моим данным, спинлок всегда быстрее, чем мьютекс, хоть на однопроцессорной, хоть на многопроцессорной системе. Единственно, когда он хуже – если предполагается делать блокировку на более-менее длительное время, и то не из-за скорости, а потому, что процессор не занимается полезным делом. Мьютекс же приостанавливает поток, но приостановка/возобновление потока – более медленные операции, чем спин-блокировка. Разве нет? Чем все-таки мьютекс более приемлемый?
2 #5: Неее, самые трудно отлавливаемые баги – с синхронизацией
Кстати, для синхронизации очень удобно использовать ООП. Например, специальный класс выполняет захват/освобождение ресурса при входе/выходе в область видимости (делая это в конструкторе/деструкторе). Таким образом, "забыть" освободить ресурс невозможно даже при наличии множественных return'ов или активной работе с исключениями C++. Вообще, весь код синхронизации сводится по сути к объявлению объекта в начале функции, вот и всё.
Даже один этот класс упрощает работу очень существенно, а можно сделать и другие. С ними вообще багов в синхронизации почти нет. Рекомендую
Правда, всё это относится только к user-time. В случае драйверов использовать C++ не рекомендуется, а жаль…
19 февраля 2009 в 0:05
to #4
Сount++ там оставлен для примера.. на самом деле там выполнялась работа по резервированию кусочка памяти в уже зарезервированном физическом пространстве.
> В любом случае, у вас неправильно написано На
> однопроцессорной машине
> spinlock не "переводит процессор в бесполезный цикл"
Помоему сейчас будет просто не вежливо писать код для однопроцессорных систем… поэтому сразу акцент был сделан на многопроцессорную машину.
Если бы не эта ситуация в которой запрос к защищенному блоку иногда приходит на уровне DISPATH_LEVEL – то как раз FAST_MUTEX был бы более приемлимым чем Spinlock… Всетаки не стоит везде использовать SpinLock…
Даже при обработке Cancel Irp всегда стараюсь как можно быстрее освободить CancelSpinLock – чтобы не деградировать систему!
19 февраля 2009 в 0:01
По статистике самые трудно отловляемые баги – самые глупые.
Сегодня два часа убил на поиск дедлока потоков – оказалось вызывался лишний ReleaseSemaphore()
18 февраля 2009 в 14:02
Почему бы для оптимизации count++ не использовать InterlockedIncrement – это наискорейший способ, не зависящий от IRQL Причем функции семейства Interlocked* есть как в user-mode, так и в kernel-mode. Это гораздо лучше, чем даже spinlock
Конечно, если действительно нужно только и всего, что count++.
В любом случае, у вас неправильно написано. На однопроцессорной машине spinlock не "переводит процессор в бесполезный цикл" – на самом деле в однопроцессорной версии ядра при захвате спинлока только повышается IRQL до DISPATCH_LEVEL, и ничего более! Поэтому, на самом деле вы сначала задумали "пессимизацию", так как мьютексы, даже быстрые (FastMutex), ВСЕГДА работают медленнее, чем спинлоки.
18 февраля 2009 в 12:01
Это скорее не оптимизация, а неучтённый побочный эффект. И IRQL здесь – как глобадьная переменная…
18 февраля 2009 в 3:03
Интересно, хоть одна душа кроме нас с тобой поняла, что же здесь написано? =) А вообще написание чего угодно для кернел мода виндоус – это лучше сразу написать новую систему и не мучаться. Ненене.