Created
March 7, 2016 05:36
-
-
Save Maccimo/15cd1e189536d797066b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- 278509.html 2016-03-07 08:32:37.670799200 +0300 | |
+++ 278509-asya.html 2016-03-07 08:33:22.384356700 +0300 | |
@@ -1,79 +1,54 @@ | |
<div class="post_show"> | |
<div class="post shortcuts_item" id="post_278509"> | |
- <div class="published">вчера в 18:39</div> | |
+ <div class="published"> 3 марта в 18:39</div> | |
<h1 class="title"> | |
- <span class="post_title">Шустрый потокобезопасный менеджер кучи и голый Си</span> | |
+ <span class="post_title">Шустрый потокобезопасный менеджер кучи, Ася и голые Си</span> | |
</h1> | |
<div class="hubs"> | |
<a href="https://habrahabr.ru/hub/programming/" class="hub subscribed" title="Вы подписаны на этот хаб">Программирование</a><span class="profiled_hub" title="Профильный хаб">*</span>, | |
<a href="https://habrahabr.ru/hub/parallel_programming/" class="hub subscribed" title="Вы подписаны на этот хаб">Параллельное программирование</a><span class="profiled_hub" title="Профильный хаб">*</span>, | |
- <a href="https://habrahabr.ru/hub/algorithms/" class="hub subscribed" title="Вы подписаны на этот хаб">Алгоритмы</a><span class="profiled_hub" title="Профильный хаб">*</span>, | |
<a href="https://habrahabr.ru/hub/cpp/" class="hub subscribed" title="Вы подписаны на этот хаб">C++</a><span class="profiled_hub" title="Профильный хаб">*</span>, | |
- <a href="https://habrahabr.ru/hub/c/" class="hub subscribed" title="Вы подписаны на этот хаб">C</a><span class="profiled_hub" title="Профильный хаб">*</span> | |
+ <a href="https://habrahabr.ru/hub/c/" class="hub subscribed" title="Вы подписаны на этот хаб">C</a><span class="profiled_hub" title="Профильный хаб">*</span>, | |
+ <a href="https://habrahabr.ru/hub/asm/" class="hub subscribed" title="Вы подписаны на этот хаб">Assembler</a><span class="profiled_hub" title="Профильный хаб">*</span> | |
</div> | |
<div class="content html_format"> | |
- текст обновлен 04.03.15 20:36<br> | |
+ текст обновлен 07.03.16 9:22<br> | |
<br> | |
При планировании любой задачи мы стремимся как можно точнее конкретизировать запросы, определить исходные данные и по возможности избавиться от любой неопределенности, мешающей просчитать конечный результат. Однако при разработке высокоуровневой логики не всегда уделяется внимание таким простым казалось бы вещам, как размещение данных в памяти, менеджмент потоков, обрабатывающих наш функционал, особенности реализации динамических массивов или бинарный интерфейс процедур. Когда написанная программа предельно лаконична и оптимизирована, но при этом работает не так быстро, как хотелось бы, закономерно возникает вопрос: «а что еще можно улучшить?» Насколько можно доверять низкоуровневому инструментарию, написанному профессиональными программистами, безусловно разбирающимися в своем деле, но при этом ни черта не понимающими в тех идеях, что вы хотите реализовать? Фрагментация, зацикленность, прерывания, события, объекты, уведомления, каждое новое знакомство с Си-шными или WinAPI-шными библиотеками подталкивает к очевидной мысли: «зачем такая громоздкая реализация?» Почему нельзя просто сделать менеджер кучи выделяющий память за строгое количество шагов? Использовать real-time статистику при работе с разделяемыми данными, вместо сложной системы семафоров и уведомлений? Наращивать динамические массивы без переразмещения и обращаться к случайной ячейке за одинаковое время после любого количества реформаций? Миссия не кажется невыполнимой. Самое время поэкспериментировать.<br> | |
<br> | |
-Предлагаю широкому вниманию процедурную реализацию менеджера кучи, способного выделять диапазоны памяти заказных размеров. Цикл поиска при этом не превышает нормированных пределов. Работа с памятью состоит всего из двух процедур: memAlloc и memRecycle, выполняющих связанные функции. Потокобезопасность поддерживается с помощью дополнительного инструментария, в свою очередь состоящего еще из нескольких процедур. О распараллеливании немного поподробнее: принцип базируется на ассоциированной блокировке разделяемых данных, схожих с блокировкой шины методами Interlocked, однако без блокировки шины. Весь процесс сводится к созданию позиционных стопоров, являющихся по сути диапазонами памяти, содержащими однобайтовые метки состояний каждого из потоков в доступном пуле. Размер выводится из этого соотношения. Поскольку я использую восьмипотоковый пул (а больше мне не нужно), то позиционные стопоры у меня занимают 8 байт (64 бита). Перед перезаписью разделяемой информации стопор блокируется исполняющим потоком, записывая метку в байт под смещением своего номера в пуле. Другие потоки не будут работать с разделяемыми данными пока стопор не обнулится, выполняя Sleep, либо откладывая задачу, либо считая овечек в цикле, на выбор программиста.<br> | |
+Предлагаю широкому вниманию процедурную реализацию менеджера кучи, способного выделять диапазоны памяти заказных размеров. Цикл поиска при этом не превышает нормированных пределов. Работа с памятью состоит всего из двух процедур: memAlloc и memRecycle, выполняющих связанные функции. Потокобезопасность поддерживается с помощью дополнительного инструментария, в свою очередь состоящего еще из нескольких процедур. О распараллеливании немного поподробнее: принцип базируется на ассоциированной блокировке разделяемых данных, схожих с блокировкой шины методами Interlocked, однако без блокировки шины. Выбор такого подхода прост — блокировка шины работает немного медленнее. Весь процесс сводится к созданию позиционных стопоров, являющихся по сути диапазонами памяти, содержащими однобайтовые метки состояний каждого из потоков в доступном пуле. Размер выводится из этого соотношения. Поскольку я использую восьмипотоковый пул (а больше мне не нужно), то позиционные стопоры у меня занимают 8 байт (64 бита). Перед перезаписью разделяемой информации стопор блокируется исполняющим потоком, записывая метку в байт под смещением своего номера в пуле. Другие потоки не будут работать с разделяемыми данными пока стопор не обнулится, выполняя Sleep, либо откладывая задачу, либо считая овечек в цикле, на выбор программиста.<br> | |
<br> | |
-<b>Забегая немного вперед опубликую результаты тестов новоизобретенных узлов велосипеда в сравнении со стандартными malloc/free</b><br> | |
-Запустим в цикле процедуру запроса/возврата отрезков памяти разных размеров, используя стандартные средства замера времени GetTickCount(). Дабы избежать обнуления сверхмалых интервалов промасштабируем цикл до 10000 шагов, чтобы оценивать время в миллисекундах.<br> | |
-<a name="habracut"></a><br> | |
-<pre><code class="cpp hljs"><span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> StartTime; | |
-<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">long</span> Elapsed; | |
-INT64 testPtr[<span class="hljs-number">1000</span>]; | |
-INT64 switcher = <span class="hljs-number">1</span>; | |
-INT64 counter = <span class="hljs-number">0</span>; | |
- | |
-StartTime = GetTickCount(); | |
-<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">100</span>; i++) { | |
- <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i2 = <span class="hljs-number">0</span>; i2 < <span class="hljs-number">100</span>; i2++) { | |
- <span class="hljs-keyword">if</span> (switcher > <span class="hljs-number">0</span>) { | |
- <span class="hljs-comment">//testPtr[i2] = malloc(i2 * 1024 + 64); //путем переключения строк тестируем каждый метод</span> | |
- testPtr[i2] = memAlloc(<span class="hljs-number">0</span>, i2 * <span class="hljs-number">1024</span> + <span class="hljs-number">64</span>, <span class="hljs-number">0</span>); | |
- } | |
- <span class="hljs-keyword">else</span> { | |
- <span class="hljs-comment">//free(testPtr[i2]);</span> | |
- memRecycle(<span class="hljs-number">0</span>, testPtr[i2], <span class="hljs-number">0</span>); | |
- } | |
- counter++; | |
- } | |
- switcher *= <span class="hljs-number">-1</span>; | |
-} | |
-Elapsed = GetTickCount() - StartTime; | |
-</code></pre><br> | |
-<br> | |
-<b>результаты:</b><br> | |
-пятикратный цикл malloc/free: 1954, 1906, 2375, 1953, 1891 = в среднем 2015 миллисекунд<br> | |
-пятикратный цикл memAlloc/memRecycle: 16, 15, 16, 16, 16 = в среднем 16 миллисекунд<br> | |
-<br> | |
-Довольно ощутимая разница особенно ярко проявляет себя при работе с огромными потоками данных, поступающих с разных направлений одновременно. Оптимизация казалось бы рутинного процесса аллоцирования памяти оказывается особенно актуальной при работе с логическими и векторными сетями, когда разнотипные данные, принадлежащие связанным множествам не могут быть записаны в массивы. Размещение таких данных осуществляется иерархически и каждый диапазон модифицируется, переразмещается и удаляется независимо. Только представьте себе чтение текста из 70 тыс строк и сохранение его логической структуры и вы сразу поймете, что процедура распределения памяти — это основа. Тот фундамент на котором должен строиться грамотный алгоритм.<br> | |
+Забегая немного вперед скажу что в сравнении со стандартными malloc/free, новоизобретенным колесам велосипеда удается работать быстрее от нескольких десятков до сотни раз. Оценить результаты многопоточных тестов можно в конце статьи. Довольно ощутимая разница особенно ярко проявляет себя при работе с масштабными потоками данных, поступающих с разных направлений одновременно. Оптимизация казалось бы рутинного процесса аллоцирования памяти оказывается особенно актуальной при работе с логическими и векторными сетями, когда разнотипные данные, принадлежащие связанным множествам не могут быть записаны в последовательные массивы. Размещение таких данных осуществляется иерархически и каждый диапазон модифицируется, переразмещается и удаляется независимо. Только представьте себе чтение текста из 70 тыс строк и сохранение его логической структуры и вы сразу поймете, что процедура распределения памяти — это основа. Тот фундамент на котором должен строиться грамотный алгоритм.<br> | |
<br> | |
<b>Особенности реализации</b><br> | |
-Основной особенностью алгоритма выделения памяти в данном менеджере является перераспределение вычислений. Часть операций, необходимых для поиска последовательного диапазона внутри фрагментированной памяти, выполняется как предподготовка при запуске функции отработки memRecycle. Идея этого заключена в делении возвращаемых отрезков на группы размерности, количество которых определяется программистом на этапе запуска процедуры-конструктора. Именно цепочка отработки проверяется первой при запросе нового диапазона. Именно количество групп размерности является величиной цикла поиска перед выделением нового диапазона. memRecycle ищет группу размерности, соответствующую размеру запрошенного отрезка + 1, после чего берет указатель на первый в цепочке диапазон, поскольку он безусловно больше запрашиваемого на одну ступень. При отсутствии в цепочках отработки диапазона необходимого размера, память берется из самой большой верхней группы, в которой хранится неразмеченый запас, величина которого также указывается при запуске конструктора. Иными словами для получения одного отрезка длины не менее запрошенной, обработчик делает однократный цикл с фиксированным количеством шагов (по группам размерности) в поисках отработанных диапазонов и берет первый попавшийся нужной размерности + 1.<br> | |
+Основной особенностью алгоритма выделения памяти в данном менеджере является перераспределение вычислений. Часть операций, необходимых для поиска последовательного диапазона внутри фрагментированной памяти, выполняется как предподготовка при запуске функции отработки memRecycle. Идея этого заключена в делении возвращаемых отрезков на группы размерности, количество которых определяется программистом на этапе запуска процедуры-конструктора. Именно цепочка отработки проверяется первой при запросе нового диапазона. Именно количество групп размерности является величиной цикла поиска перед выделением нового диапазона. memAlloc ищет группу размерности, соответствующую размеру запрошенного отрезка + 1, после чего берет указатель на первый в цепочке диапазон, поскольку он безусловно больше запрашиваемого на одну ступень. При отсутствии в цепочках отработки диапазона необходимого размера, память берется из самой большой верхней группы, в которой хранится неразмеченый запас, величина которого также указывается при запуске конструктора. <br> | |
+<br> | |
+Таким образом для получения одного отрезка длины не менее запрошенной, обработчик делает однократный цикл с фиксированным количеством шагов (по группам размерности) в поисках отработанных диапазонов и берет первый попавшийся нужной размерности + 1. Для возврата отрезка обратно memRecycle выполняет еще меньше действий, всего лишь удлиняя цепочку нужной группы размерности еще одним указателем. Без циклов. Это нехитрой последовательностью действий достигается высокая производительность: что такое для современных процессоров цикл из двух сотен проверок опустошения указателя, когда они способны исполнять циклы из миллионов шагов менее чем за 1 миллисекунду.<br> | |
<br> | |
<img src="https://habrastorage.org/files/746/c59/c0a/746c59c0a8fc4385adcd05ce783759cb.jpg"><br> | |
<br> | |
Реализация потокобезопасности является вариацией на тему программно-транзакционной памяти и осуществляется ступенчатым процессом, представляющим собой одну неразрывную логическую последовательность. Условие для получения эксклюзивного доступа к разделяемому диапазону памяти достигается тривиальной установкой битовой метки в логически связанный позиционный стопор, в котором для каждого потока из пула выделен собственный байт. ASM-процедура перед обращением к разделяемой памяти проверяет стопор на нуль, при соответствии записывая метку в байт со смещением номера потока. После чего стопор подвергается проверке по маске, в которой должен присутствовать только один бит под нужным смещением. Несоответствие означает одновременное редактирование стопора параллельными потоками, процедура возвращает нуль. Каждый поток, получивший нуль при попытке перезаписи разделяемых данных уходит в цикл ожидания, реализация которого может подбираться на свой вкус, после чего следует повторная попытка. При соответствии маски процедура возвращает единичку, что свидетельствует об эксклюзивном доступе к данным.<br> | |
<br> | |
-Листинг ассемблерной процедуры (стоит отметить, что при работе вне пула, в потоке не имеющем номера, все же используется блокировка шины):<br> | |
-<cut><br> | |
+Стоит отметить, что при работе вне пула, в потоке не имеющем номера из пула, все же используется блокировка шины. Важным моментом в свете этого решения является количество потоков, допущенных для работы с разделяемыми данными. Поскольку в данной реализации оно ограничено оптимальным с точки зрения операционных издержек числом 8, а также используются стопоры размером 8х8=64 бита, то все инструкции используют qword ptr, для чтения или обработки. Создание оперативного пула для выполнения задач требует минимальной настройки исходных данных, а именно: присвоение уникальных номеров от нуля до значения workPoolMax, определяемого программистом и согласуемого с размером стопоров. Если планируется доступ к разделяемым данным вне пула, например из main-потока, висящего в ожидании пользовательского ввода или других, необходимо присвоить им номера меньше нуля (или больше workPoolMax, с добавлением соответствующих условий в процедуры) и вместе с этим сократить размер рабочего пула, чтобы не превышать общее кол-во потоков, допущенных к работе с разделяемыми данными.<br> | |
+<br> | |
+<img src="https://habrastorage.org/files/921/a74/4a2/921a744a2e0446728f01b9ae8ae51a16.jpg"><br> | |
+<br> | |
+Листинг ассемблерной процедуры:<br> | |
+<a name="habracut"></a><br> | |
<code>cnsBit PROC ;check and set bit<br> | |
cmp rdx,0 ;первичная проверка на нумерацию потока<br> | |
jl @negtv ;отрицательные значения не относятся к нумерованному пулу и обрабатываются через InterlockedExchange<br> | |
<br> | |
cmp qword ptr [rcx],0 ;проверка метки на нуль<br> | |
@@ -81,36 +56,42 @@ | |
<br> | |
mov r8,rdx ;подготовка номера потока для смещения<br> | |
mov rax,8 ;подготовка множителя, биты устанавливаются в начале каждого байта<br> | |
mul rdx ;умножение для получения итогового смещения<br> | |
<br> | |
-mov byte ptr [rcx+r8],1 ;установка бита по нужному адресу<br> | |
+mov byte ptr [rcx+r8],1 ;установка значения по нужному адресу<br> | |
bts rdx,rax ;установка бита в маске по необходимому смещению<br> | |
<br> | |
cmp qword ptr [rcx],rdx ;проверка метки по маске<br> | |
jnz @end ;выход при несоответствии<br> | |
<br> | |
@scess: ;блок соответствия<br> | |
mov rax,1 ;собственный бит присутствует в диапазоне, сторонние биты отсутствуют<br> | |
ret<br> | |
+<br> | |
@end: ;блок несоответствия<br> | |
- mov byte ptr [rcx+r8],0 ;удаление бита из метки<br> | |
+ mov byte ptr [rcx+r8],0 ;обнуление байта в стопоре<br> | |
+<br> | |
@fin: ;блок безусловного выхода<br> | |
mov rax,0 ;отсутствует собственный бит, либо присутствуют сторонние биты<br> | |
ret<br> | |
<br> | |
@negtv: ;блок для ненумерованных потоков<br> | |
xor rax,rax ;сравниваемый операнд равен нулю<br> | |
+ mov rdx, -1 ;стопор будет заполнен битами по всему объему<br> | |
lock cmpxchg qword ptr[rcx],rdx ;compare exchange<br> | |
jnz @fin<br> | |
jmp @scess<br> | |
cnsBit ENDP<br> | |
</code><br> | |
<br> | |
Клоны Interlocked процедур — threadExchange, threadCompareExchange и threadExchangeAdd выполняют те же функции, что и оригиналы. Наверно их реализация не заслуживает внимания, однако кому-то возможно пригодится:<br> | |
<cut><br> | |
-<pre><code class="cpp hljs">threadRepeatCounter[<span class="hljs-number">7</span>]; <span class="hljs-comment">//счетчик повторов</span> | |
+<pre><code class="cpp hljs">threadRepeatCounterArray = [<span class="hljs-number">9</span>]; | |
+*threadRepeatCounter = &threadRepeatCounterArray[<span class="hljs-number">1</span>] ; <span class="hljs-comment">//счетчик повторов</span> | |
+INT64 threadSleepTimer = <span class="hljs-number">1000</span>; | |
+ | |
<span class="hljs-function">INT64 <span class="hljs-title">threadExchangeAdd</span><span class="hljs-params">(INT64 threadNum, INT64 *counter, INT64 *stopper, INT64 value)</span> <span class="hljs-comment">//итерация счетчиков. переданное значение прибавляется к находящемуся в целевом диапазоне, изначальное возвращается.</span> | |
</span>{ | |
INT64 ret = <span class="hljs-number">0</span>; | |
<span class="hljs-keyword">for</span> (;;) { | |
<span class="hljs-keyword">if</span> (cnsBit((INT64)stopper, threadNum) == <span class="hljs-number">1</span>) { | |
@@ -118,11 +99,12 @@ | |
*counter += value; | |
*stopper = <span class="hljs-number">0</span>; | |
<span class="hljs-keyword">break</span>; | |
} | |
threadRepeatCounter[threadNum] += <span class="hljs-number">1</span>; | |
- Sleep(<span class="hljs-number">1</span>); | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < threadSleepTimer * (threadNum + <span class="hljs-number">1</span>); i++) | |
+ Sleep(<span class="hljs-number">0</span>); | |
} | |
<span class="hljs-keyword">return</span> ret; | |
} | |
@@ -135,11 +117,12 @@ | |
*pointer = value; | |
*stopper = <span class="hljs-number">0</span>; | |
<span class="hljs-keyword">break</span>; | |
} | |
threadRepeatCounter[threadNum] += <span class="hljs-number">1</span>; | |
- Sleep(<span class="hljs-number">1</span>); | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < threadSleepTimer * (threadNum + <span class="hljs-number">1</span>); i++) | |
+ Sleep(<span class="hljs-number">0</span>); | |
} | |
<span class="hljs-keyword">return</span> ret; | |
} | |
@@ -154,11 +137,12 @@ | |
} | |
*stopper = <span class="hljs-number">0</span>; | |
<span class="hljs-keyword">break</span>; | |
} | |
threadRepeatCounter[threadNum] += <span class="hljs-number">1</span>; | |
- Sleep(<span class="hljs-number">1</span>); | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < threadSleepTimer * (threadNum + <span class="hljs-number">1</span>); i++) | |
+ Sleep(<span class="hljs-number">0</span>); | |
} | |
<span class="hljs-keyword">return</span> ret; | |
} | |
</code></pre><br> | |
<br> | |
@@ -191,15 +175,16 @@ | |
INT64 pInvalidAddress = <span class="hljs-number">0</span>; | |
INT64 *baseMemStore = <span class="hljs-number">0</span>; | |
INT64 *memRecycleStore = <span class="hljs-number">0</span>; <span class="hljs-comment">//служебный диапазон указателей на размеченную память</span> | |
INT64 *memRecyclePoint = <span class="hljs-number">0</span>; <span class="hljs-comment">//точки входа в цепочки блоков указателей</span> | |
-INT64 memRecycleBlock = <span class="hljs-number">0</span>; <span class="hljs-comment">//указатель на крайний выделенный блок в общем диапазоне</span> | |
+INT64 memRecycleBlock = <span class="hljs-number">0</span>; <span class="hljs-comment">//указатель на цепочку блоков отработки</span> | |
INT64 memClear = <span class="hljs-number">0</span>; <span class="hljs-comment">//модификатор для побайтового обнуления выделенного отрезка</span> | |
INT64 memNoDefrag = <span class="hljs-number">0</span>; <span class="hljs-comment">//модификатор для переработки без дефрагментации</span> | |
INT64 memBlockLimit = <span class="hljs-number">1000</span>; <span class="hljs-comment">//максимальный лимит блока для отработанных отрезков памяти</span> | |
-INT64 memFragmentCount = <span class="hljs-number">0</span>; <span class="hljs-comment">//количество фрагментированных отрезков памяти</span> | |
+INT64 memFragmentCountArray[<span class="hljs-number">9</span>]; <span class="hljs-comment">//количество фрагментированных отрезков памяти для статистики</span> | |
+INT64 *memFragmentCount = &memFragmentCountArray[<span class="hljs-number">1</span>]; | |
INT64 memGroupLimit = <span class="hljs-number">200</span>; <span class="hljs-comment">//лимит групп размерностей (предел цикла поиска перед размещением)</span> | |
INT64 memStoreSize = <span class="hljs-number">1024</span> * <span class="hljs-number">256</span> * <span class="hljs-number">4096</span>; <span class="hljs-comment">//предельный размер кучи</span> | |
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">memConstruct</span><span class="hljs-params">()</span> </span>{ | |
GetSystemInfo(&sSysInfo); | |
@@ -217,12 +202,12 @@ | |
INT64 workPtr = <span class="hljs-number">0</span>; | |
INT64 *chainPtr = <span class="hljs-number">0</span>; | |
INT64 tempVal = <span class="hljs-number">0</span>; | |
INT32 sizeGroup = <span class="hljs-number">0</span>; | |
- <span class="hljs-keyword">if</span> (size < <span class="hljs-number">3200</span>) | |
<span class="hljs-comment">//проверка нижнего подмножества малых отрезков</span> | |
+ <span class="hljs-keyword">if</span> (size < <span class="hljs-number">3200</span>) | |
sizeGroup = size / <span class="hljs-number">32</span>; | |
<span class="hljs-comment">//проверка верхнего подмножества больших отрезков</span> | |
<span class="hljs-keyword">else</span> sizeGroup = size / (<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>) + <span class="hljs-number">100</span>; | |
groupSearch: | |
@@ -253,31 +238,34 @@ | |
chainPtr = memRecyclePoint[sizeGroup]; | |
<span class="hljs-keyword">if</span> ((INT64)chainPtr == <span class="hljs-number">0</span>) | |
<span class="hljs-keyword">goto</span> getStore; | |
} | |
chainRead: | |
- <span class="hljs-comment">//проверка соответствия числа помещенных в блок указателей и извлеченных</span> | |
- <span class="hljs-keyword">if</span> (chainPtr[<span class="hljs-number">-3</span>] < chainPtr[<span class="hljs-number">-2</span>]) { | |
+ <span class="hljs-comment">//проверка соответствия числа помещенных в блок указателей и извлеченных с контролем превышения</span> | |
+ <span class="hljs-keyword">if</span> (chainPtr[<span class="hljs-number">-3</span>] < chainPtr[<span class="hljs-number">-2</span>] && chainPtr[<span class="hljs-number">-3</span>] < memBlockLimit) { | |
workPtr = threadExchange(threadNum, &chainPtr[chainPtr[<span class="hljs-number">-3</span>]], &chainPtr[<span class="hljs-number">-1</span>], <span class="hljs-number">0</span>); | |
<span class="hljs-comment">//итерация счетчика извлеченных указателей</span> | |
threadExchangeAdd(threadNum, &chainPtr[<span class="hljs-number">-3</span>], &chainPtr[<span class="hljs-number">-1</span>], <span class="hljs-number">1</span>); | |
<span class="hljs-keyword">if</span> ((INT64)workPtr == <span class="hljs-number">0</span>) | |
<span class="hljs-keyword">goto</span> chainRead; | |
<span class="hljs-comment">//фрагментация отрезка памяти</span> | |
<span class="hljs-comment">//несоответствие доступного размера отрезка и заявленного требует дополнительной обработки</span> | |
tempVal = *(INT64*)(workPtr - <span class="hljs-number">8</span>); | |
- <span class="hljs-comment">//если взятый из отработки отрезок слишком велик, процедура образует из него два отрезка. Ненужный будет возвращен в свою группу размерности.</span> | |
+ <span class="hljs-comment">//Если отрезок слишком велик, он делится на два. Остаток помещается в свою группу размерности.</span> | |
<span class="hljs-keyword">if</span> (tempVal - size > <span class="hljs-number">3200</span>) { | |
*(INT64*)(workPtr + size + <span class="hljs-number">24</span>) = tempVal - size - <span class="hljs-number">32</span>; | |
*(INT64*)(workPtr + tempVal) = (INT64)workPtr + size + <span class="hljs-number">32</span>; | |
*(INT64*)(workPtr + tempVal + <span class="hljs-number">8</span>) = *(INT64*)(workPtr + tempVal + <span class="hljs-number">16</span>) = <span class="hljs-number">0</span>; | |
<span class="hljs-comment">//отделенный лишний отрезок уходит на переработку</span> | |
memRecycle(threadNum, (INT64)workPtr + size + <span class="hljs-number">32</span>, &memNoDefrag); | |
} | |
- <span class="hljs-comment">//поскольку размер найденного отрезка может оказаться незначительно больше заявленного, происходит индексирование</span> | |
+ <span class="hljs-comment">//размер найденного необработанного отрезка может оказаться больше заявленного, происходит индексирование.</span> | |
<span class="hljs-keyword">else</span> size = *(INT64*)(workPtr - <span class="hljs-number">8</span>); | |
+ | |
+<span class="hljs-comment">//индексирование количества фрагментированных отрезков для статистики</span> | |
+memFragmentCount[threadNum]--; | |
} | |
<span class="hljs-comment">//если рабочие указатели в данный момент отсутствуют, отрезок берется из запаса</span> | |
<span class="hljs-keyword">else</span> <span class="hljs-keyword">goto</span> getStore; | |
} | |
<span class="hljs-comment">//если группы подходящей размерности отсутствуют, новый отрезок берется из запаса</span> | |
@@ -324,47 +312,58 @@ | |
<span class="hljs-keyword">if</span> (param == &memNoDefrag) | |
<span class="hljs-keyword">goto</span> recycle; | |
<span class="hljs-comment">//дефрагментация освобожденных ранее отрезков</span> | |
<span class="hljs-keyword">for</span> (;;) { | |
- <span class="hljs-comment">//сверка указателей на предыдущий отрезок и блок отработки</span> | |
- <span class="hljs-keyword">if</span> (*(INT64*)(workPtr - <span class="hljs-number">16</span>) == <span class="hljs-number">0</span> || **(INT64**)(workPtr - <span class="hljs-number">16</span>) != *(INT64*)(workPtr - <span class="hljs-number">32</span>)) | |
+ <span class="hljs-comment">//сверка указателей блока отработки и начала текущего отрезка. при соответствии отрезок считается отработанным.</span> | |
+ tempPtr = *(INT64*)(workPtr - <span class="hljs-number">16</span>); | |
+ <span class="hljs-keyword">if</span> ((INT64)tempPtr == <span class="hljs-number">0</span> || *tempPtr != *(INT64*)(workPtr - <span class="hljs-number">32</span>)) | |
<span class="hljs-keyword">break</span>; | |
- tempVal = threadExchange(threadNum, *(INT64*)(workPtr - <span class="hljs-number">16</span>), *(INT64*)(workPtr - <span class="hljs-number">24</span>) - <span class="hljs-number">8</span>, <span class="hljs-number">0</span>); | |
+ tempVal = threadExchange(threadNum, (INT64)tempPtr, &memRecycleStore[<span class="hljs-number">1</span>], <span class="hljs-number">0</span>); | |
<span class="hljs-comment">//если указатель на отрезок в блоке отработки был обнулен в параллельном потоке, процедура завершает поиск</span> | |
<span class="hljs-keyword">if</span> (tempVal == <span class="hljs-number">0</span>) | |
<span class="hljs-keyword">break</span>; | |
<span class="hljs-comment">//при объединении отрезков, размер текущего прибавляется к следующему ниже в диапазоне</span> | |
tempSize = *(INT64*)(workPtr - <span class="hljs-number">8</span>); | |
workPtr = *(INT64*)(workPtr - <span class="hljs-number">32</span>); | |
+ | |
*(INT64*)(workPtr - <span class="hljs-number">8</span>) += (tempSize + <span class="hljs-number">32</span>); | |
+ | |
+<span class="hljs-comment">//индексирование количества фрагментированных отрезков для статистики</span> | |
+memFragmentCount[threadNum]--; | |
} | |
<span class="hljs-comment">//итоговый размер обрабатываемого отрезка дополнительно сохраняется после всех преобразований</span> | |
tempSize = *(INT64*)(workPtr - <span class="hljs-number">8</span>); | |
+ | |
<span class="hljs-keyword">for</span> (;;) { | |
<span class="hljs-comment">//проверка заполнения следующего блока</span> | |
- <span class="hljs-keyword">if</span> (*(INT64*)(workPtr + tempSize + <span class="hljs-number">24</span>) == <span class="hljs-number">0</span>) | |
+ tempVal = *(INT64*)(workPtr + tempSize + <span class="hljs-number">24</span>); | |
+ <span class="hljs-keyword">if</span> (tempVal == <span class="hljs-number">0</span>) | |
<span class="hljs-keyword">break</span>; | |
<span class="hljs-comment">//размер впереди идущего отрезка прибавляются сразу, все дальнейшие вычисления идут с необходимым смещением.</span> | |
- tempSize += *(INT64*)(workPtr + tempSize + <span class="hljs-number">24</span>) + <span class="hljs-number">32</span>; | |
- <span class="hljs-comment">//сверка указателей на следующий отрезок и блок отработки</span> | |
- <span class="hljs-keyword">if</span> (*(INT64*)(workPtr + tempSize + <span class="hljs-number">16</span>) == <span class="hljs-number">0</span> || **(INT64**)(workPtr + tempSize + <span class="hljs-number">16</span>) != *(INT64*)(workPtr + tempSize)) | |
+ tempSize += tempVal + <span class="hljs-number">32</span>; | |
+ tempPtr = *(INT64*)(workPtr + tempSize + <span class="hljs-number">16</span>); | |
+ <span class="hljs-comment">//сверка указателей блока отработки и начала текущего отрезка. при соответствии отрезок считается отработанным.</span> | |
+ <span class="hljs-keyword">if</span> ((INT64)tempPtr == <span class="hljs-number">0</span> || *tempPtr != *(INT64*)(workPtr + tempSize)) | |
<span class="hljs-keyword">break</span>; | |
- tempVal = threadExchange(threadNum, *(INT64*)(workPtr + tempSize + <span class="hljs-number">16</span>), *(INT64*)(workPtr + tempSize + <span class="hljs-number">8</span>) - <span class="hljs-number">8</span>, <span class="hljs-number">0</span>); | |
+ tempVal = threadExchange(threadNum, (INT64)tempPtr, &memRecycleStore[<span class="hljs-number">1</span>], <span class="hljs-number">0</span>); | |
<span class="hljs-comment">//если указатель на отрезок в блоке отработки был обнулен в параллельном потоке, процедура завершает поиск</span> | |
<span class="hljs-keyword">if</span> (tempVal == <span class="hljs-number">0</span>) | |
<span class="hljs-keyword">break</span>; | |
<span class="hljs-comment">//при объединении заранее проиндексированный размер фиксируется в обрабатываемом отрезке</span> | |
*(INT64*)(workPtr - <span class="hljs-number">8</span>) = tempSize; | |
+ | |
+<span class="hljs-comment">//индексирование количества фрагментированных отрезков для статистики</span> | |
+memFragmentCount[threadNum]--; | |
} | |
-recycle: | |
<span class="hljs-comment">//настройка блоков указателей</span> | |
+recycle: | |
<span class="hljs-comment">//итоговый размер обрабатываемого отрезка дополнительно сохраняется после всех преобразований</span> | |
tempSize = *(INT64*)(workPtr - <span class="hljs-number">8</span>); | |
- <span class="hljs-keyword">if</span> (tempSize < <span class="hljs-number">3200</span>) | |
<span class="hljs-comment">//проверка нижнего подмножества малых отрезков</span> | |
+ <span class="hljs-keyword">if</span> (tempSize < <span class="hljs-number">3200</span>) | |
sizeGroup = tempSize / <span class="hljs-number">32</span>; | |
<span class="hljs-comment">//проверка верхнего подмножества больших отрезков</span> | |
<span class="hljs-keyword">else</span> sizeGroup = tempSize / (<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>) + <span class="hljs-number">100</span>; | |
<span class="hljs-comment">//превышение пределов размерностей переводит указатель на крайнюю группу</span> | |
sizeGroup = (sizeGroup > memGroupLimit) ? memGroupLimit : sizeGroup; | |
@@ -392,10 +391,17 @@ | |
<span class="hljs-comment">//итерация счетчика добавления указателей. указатель на отрезок будет добавлен в блок под полученным номером.</span> | |
tempVal = threadExchangeAdd(threadNum, &tempPtr[<span class="hljs-number">-2</span>], &tempPtr[<span class="hljs-number">-1</span>], <span class="hljs-number">1</span>); | |
<span class="hljs-comment">//если блок переполнен, создается новый</span> | |
<span class="hljs-keyword">if</span> (tempVal > memBlockLimit) | |
<span class="hljs-keyword">goto</span> createBlock; | |
+ | |
+ <span class="hljs-comment">//отработка отрезка</span> | |
+ <span class="hljs-comment">//после необходимых преобразований указатель на начало отрезка модифицируется</span> | |
+ *(INT64*)(workPtr + tempSize) = workPtr; | |
+ <span class="hljs-comment">//указатель на блок отработки и позицию для сброса при дальнейшей дефрагментации</span> | |
+ *(INT64*)(workPtr + tempSize + <span class="hljs-number">8</span>) = (INT64)tempPtr; | |
+ *(INT64*)(workPtr + tempSize + <span class="hljs-number">16</span>) = &tempPtr[tempVal]; | |
<span class="hljs-comment">//запись указателя на отработанный отрезок памяти в блок под нужным номером</span> | |
tempPtr[tempVal] = workPtr; | |
<span class="hljs-comment">//реструктурирование цепочки групп размерности</span> | |
<span class="hljs-comment">//при создании нового блока указателей, необходимо встроить его в цепь</span> | |
@@ -408,78 +414,137 @@ | |
} | |
<span class="hljs-comment">//указатель на новый блок помещается в точку входа, если она обнулена изначально, либо за время процедурных действий</span> | |
<span class="hljs-keyword">if</span> (memRecyclePoint[sizeGroup] == <span class="hljs-number">0</span>) | |
memRecyclePoint[sizeGroup] = (INT64)tempPtr; | |
- <span class="hljs-comment">//финализация</span> | |
- <span class="hljs-comment">//после всех преобразований указатель на начало отрезка модифицируется</span> | |
- *(INT64*)(workPtr + tempSize) = workPtr; | |
- <span class="hljs-comment">//указатель на блок отработки и позицию для сброса при дальнейшей дефрагментации</span> | |
- *(INT64*)(workPtr + tempSize + <span class="hljs-number">8</span>) = (INT64)tempPtr; | |
- *(INT64*)(workPtr + tempSize + <span class="hljs-number">16</span>) = &tempPtr[tempVal]; | |
+<span class="hljs-comment">//индексирование количества фрагментированных отрезков для статистики</span> | |
+memFragmentCount[threadNum]++; | |
} | |
</code></pre><br> | |
<cut><br> | |
<pre><code class="cpp hljs"><span class="hljs-function">INT64 <span class="hljs-title">memPtrCheck</span><span class="hljs-params">(INT64 *ptr)</span> <span class="hljs-comment">//процедура проверки адреса на соответствие диапазону памяти кучи</span> | |
</span>{ | |
<span class="hljs-keyword">if</span> ((<span class="hljs-keyword">unsigned</span> __int64)ptr > (<span class="hljs-keyword">unsigned</span> __int64)baseMemStore && (<span class="hljs-keyword">unsigned</span> __int64)ptr <= (<span class="hljs-keyword">unsigned</span> __int64)*baseMemStore) | |
<span class="hljs-keyword">return</span> *ptr; | |
<span class="hljs-keyword">return</span> &pInvalidAddress; | |
} | |
-</code></pre> | |
+</code></pre><br> | |
+<br> | |
+<b>Многопоточное тестирование</b><br> | |
+Чтобы выяснить как менеджер ведет себя в более реальных условиях запустим цикл из 10000 шагов с выделением отрезков памяти случайного размера, генерируемого функцией rand(). Напишем процедуру timeTestSub() и запустим в восьми потоках одновременно. Точное время выполнения попробуем замерить методами QueryPerformanceCounter/Frequency. Дабы быть уверенным, что потоки честным образом выполнялись одновременно используем счетчики пересечений, фиксирующие количество перезапусков threadExchange-процедур. А чтобы наблюдать сложность структуры и фрагментированность кучи используем счетчик цепи, показывающий количество фрагментов, находящихся в отработке.<br> | |
+<br> | |
+<pre><code class="cpp hljs">LARGE_INTEGER timerFrequency; | |
+<span class="hljs-keyword">double</span> timeResult[<span class="hljs-number">8</span>]; | |
+<span class="hljs-keyword">double</span> timeSum = <span class="hljs-number">0</span>; | |
+INT64 testPtr[<span class="hljs-number">8</span>][<span class="hljs-number">100</span>]; | |
+HANDLE ghEvents[<span class="hljs-number">8</span>]; | |
+HANDLE hHeap; | |
+INT64 testStpr = <span class="hljs-number">0</span>; | |
+INT64 testVal = <span class="hljs-number">0</span>; | |
+INT64 repeatCounter = <span class="hljs-number">0</span>; | |
+INT64 fragmentCounter = <span class="hljs-number">0</span>; | |
+ | |
+<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">timeTestSub</span><span class="hljs-params">(INT64 threadNum)</span> | |
+</span>{ | |
+ INT64 switcher = <span class="hljs-number">1</span>; | |
+ LARGE_INTEGER timerStart; | |
+ LARGE_INTEGER timerStop; | |
+ QueryPerformanceCounter(&timerStart); | |
+ | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">100</span>; i++) { | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i2 = <span class="hljs-number">0</span>; i2 < <span class="hljs-number">100</span>; i2++) { | |
+ <span class="hljs-keyword">if</span> (switcher > <span class="hljs-number">0</span>) { | |
+ testPtr[threadNum][i2] = memAlloc(threadNum, rand(), <span class="hljs-number">0</span>); | |
+ <span class="hljs-comment">//testPtr[threadNum][i2] = malloc(rand()); //путем переключения строк тестируем каждый метод</span> | |
+ <span class="hljs-comment">//testPtr[threadNum][i2] = (INT64)HeapAlloc(hHeap, 0, rand());</span> | |
+ } | |
+ <span class="hljs-keyword">else</span> { | |
+ memRecycle(threadNum, testPtr[threadNum][i2], <span class="hljs-number">0</span>); | |
+ <span class="hljs-comment">//free(testPtr[threadNum][i2]);</span> | |
+ <span class="hljs-comment">//HeapFree(hHeap, 0, (LPVOID)testPtr[threadNum][i2]);</span> | |
+ } | |
+ } | |
+ switcher *= <span class="hljs-number">-1</span>; | |
+ } | |
+ | |
+ QueryPerformanceCounter(&timerStop); | |
+ timeResult[threadNum] = timerStop.QuadPart - timerStart.QuadPart; | |
+ timeResult[threadNum] = timeResult[threadNum] / timerFrequency.QuadPart; | |
+ SetEvent(ghEvents[threadNum]); | |
+} | |
+ | |
+<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> | |
+</span>{ | |
+ hHeap = HeapCreate(<span class="hljs-number">0</span>, <span class="hljs-number">1024</span> * <span class="hljs-number">256</span> * <span class="hljs-number">4096</span>, <span class="hljs-number">0</span>); | |
+ QueryPerformanceFrequency(&timerFrequency); | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">8</span>; i++) | |
+ ghEvents[i] = CreateEvent(<span class="hljs-literal">NULL</span>, FALSE, FALSE, <span class="hljs-literal">NULL</span>); | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">8</span>; i++) { | |
+ _beginthreadex(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, timeTestSub, (<span class="hljs-keyword">void</span>*)i, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>); | |
+ } | |
+ WaitForMultipleObjects(<span class="hljs-number">8</span>, ghEvents, TRUE, <span class="hljs-number">10000</span>); | |
+ <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">8</span>; i++) { | |
+ timeSum += timeResult[i]; | |
+ repeatCounter += threadRepeatCounter[i]; | |
+ fragmentCounter += memFragmentCount[i]; | |
+ } | |
+} | |
+</code></pre><br> | |
+<br> | |
+<img src="https://habrastorage.org/files/a51/86b/dbd/a5186bdbdbb54baeb50650ef054223f4.jpg"> | |
<div class="clear"></div> | |
- </cut></cut></cut></cut></cut></cut></div> | |
+ </cut></cut></cut></cut></cut></div> | |
<ul class="tags icon_tag"> | |
- <li><a href="https://habrahabr.ru/search/?q=%5B%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%3B%20c%3B%20c%2B%2B%3B%5D&target_type=posts" rel="tag">программирование; c; c++;</a></li> | |
+ <li><a href="https://habrahabr.ru/search/?q=%5B%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%3B%20c%3B%20c%2B%2B%3B%20asm%3B%5D&target_type=posts" rel="tag">программирование; c; c++; asm;</a></li> | |
</ul> | |
<div class="infopanel_wrapper js-user_33766"> | |
<ul class="postinfo-panel postinfo-panel_post" id="infopanel_post_278509"> | |
<li class="postinfo-panel__item"> | |
- <div class="voting-wjt voting-wjt_infopanel js-voting voted"> | |
- <button disabled="disabled" type="button" class="voting-wjt__button voting-wjt__button_plus js-plus" title="Нравится" onclick="return posts_vote(this, 278509, '2', 1)"> | |
+ <div class="voting-wjt voting-wjt_infopanel js-voting "> | |
+ <button type="button" disabled="" class="voting-wjt__button voting-wjt__button_plus js-plus" title="Срок голосования истек."> | |
<span>↑</span> | |
</button> | |
- <div class="voting-wjt__counter js-mark voting-wjt__counter_positive"> | |
- <span class="voting-wjt__counter-score js-score" title="Всего 26: ↑23 и ↓3">+20</span> | |
+ <div class="voting-wjt__counter voting-wjt__counter_positive js-mark"> | |
+ <span class="voting-wjt__counter-score js-score" title="Общий рейтинг 28: ↑24 и ↓4">+20</span> | |
</div> | |
- <button disabled="disabled" type="button" class="voting-wjt__button voting-wjt__button_minus js-minus" title="Не нравится" onclick="return posts_vote(this, 278509, '2', -1)"> | |
+ <button type="button" disabled="" class="voting-wjt__button voting-wjt__button_minus js-minus" title="Срок голосования истек."> | |
<span>↓</span> | |
</button> | |
</div> | |
</li> | |
<li class="postinfo-panel__item"> | |
- <div class="views-count_post" title="Просмотры публикации">9,4k</div> | |
+ <div class="views-count_post" title="Просмотры публикации">12,1k</div> | |
</li> | |
<li class="postinfo-panel__item"> | |
<div class="favorite-wjt favorite"> | |
<button type="button" class="favorite-wjt__button add" title="Добавить в избранное" onclick="return posts_add_to_favorite(this, '2', 278509);"> | |
<span>В избранное</span> | |
</button> | |
- <span class="favorite-wjt__counter js-favs_count" title="Количество пользователей, добавивших публикацию в избранное">96</span> | |
+ <span class="favorite-wjt__counter js-favs_count" title="Количество пользователей, добавивших публикацию в избранное">133</span> | |
</div> | |
</li> | |
<!-- шеринг в соцсети --> | |
<li class="postinfo-panel__item postinfo-panel__item_socials postinfo-panel__item_socials_right "> | |
<ul class="post-share"> | |
<li class="post-share__item"> | |
- <a href="https://twitter.com/intent/tweet?text=%D0%A8%D1%83%D1%81%D1%82%D1%80%D1%8B%D0%B9+%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D1%8B%D0%B9+%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80+%D0%BA%D1%83%D1%87%D0%B8+%D0%B8+%D0%B3%D0%BE%D0%BB%D1%8B%D0%B9+%D0%A1%D0%B8+http://habr.ru/p/278509/+via+%40habrahabr+%23habr" class="post-share__item-link twitter" title="Опубликовать ссылку в Twitter" target="_blank"></a> | |
+ <a href="https://twitter.com/intent/tweet?text=%D0%A8%D1%83%D1%81%D1%82%D1%80%D1%8B%D0%B9+%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D1%8B%D0%B9+%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80+%D0%BA%D1%83%D1%87%D0%B8%2C+%D0%90%D1%81%D1%8F+%D0%B8+%D0%B3%D0%BE%D0%BB%D1%8B%D0%B5+%D0%A1%D0%B8+http://habr.ru/p/278509/+via+%40habrahabr+%23habr" class="post-share__item-link twitter" title="Опубликовать ссылку в Twitter" target="_blank"></a> | |
</li> | |
<li class="post-share__item"> | |
<a href="https://vk.com/share.php?url=https://habrahabr.ru/post/278509/" class="post-share__item-link vkontakte" title="Опубликовать ссылку во ВКонтакте" onclick="window.open(this.href, 'Опубликовать ссылку во Вконтакте', 'width=800,height=300'); return false"></a> | |
</li> | |
<li class="post-share__item"> | |
@@ -503,22 +568,22 @@ | |
<span class="user-pic_default user-pic_default_47 user-pic_lilac"></span> | |
</a> | |
<div class="author-info__desc js-user_33766"> | |
<div class="author-info__username"> | |
- <a href="/users/Tatuin" class="author-info__name">Татьяна</a> | |
+ <a href="/users/Tatuin" class="author-info__name">Дмитрий</a> | |
<a href="/users/Tatuin" class="author-info__nickname">@Tatuin</a> | |
<div class="karma__widjet voting-wjt voting-wjt_small js-karma " title="Карма пользователя"> | |
<button type="button" class="voting-wjt__button voting-wjt__button_plus js-vote_plus" onclick="return userKarmaVote(this, 33766, 1, 1);" title="Повысить карму"> | |
<span>↑</span> | |
</button> | |
- <div class="voting-wjt__counter js-karma-mark voting-wjt__counter_positive " title="12 голосов"> | |
+ <div class="voting-wjt__counter js-karma-mark voting-wjt__counter_positive " title="16 голосов"> | |
<div class="voting-wjt__label">карма</div> | |
- <div class="voting-wjt__counter-score js-karma_num">10,0</div> | |
+ <div class="voting-wjt__counter-score js-karma_num">14,0</div> | |
</div> | |
<button type="button" class="voting-wjt__button voting-wjt__button_minus js-vote_minus" onclick="return userKarmaVote(this, 33766, 1, -1);" title=" Понизить карму"> | |
<span>↓</span> | |
</button> | |
@@ -534,10 +599,13 @@ | |
Написать | |
</a> | |
</div> | |
</div> | |
+ <div class="author-info__specialization"> | |
+ системостроитель | |
+ </div> | |
</div> | |
</div> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment