Конференция "Media" » Доступ к блоку памяти из TThread
 
  • Dmk © (09.03.16 18:18) [0]
    Привет! Требуется ускорить процесс обработки графических данных. Возможно ли обрабатывать массив из потоков и ускорит ли выделение в поток функций обработки? Вроде видел тут Sapersky выкладывал демку с отрисовкой в потоках. Там скорость за 1000 fps зашкаливала.
  • Pavia © (09.03.16 19:49) [1]
    Dmk, ты ли это?
    Привет. Да ускоряется. Скорость зависит от числа вычислительных блоков. То бишь ядер.

    1000 fps что-то не верится. Хотя тут вроде была заливка белым цветом.
    У меня функции обработки работают со следующей скоростью. Все тесты проводились на 512х512.
    0.7 мс Инвертирования цвета.
    10-15 мс фильтрация типа Blur.
    30-50 мс HSV, Кэнни, Угловой детектор.

    Распределение задач можно сделать по кадрам. Соответственно за х мс будет 4 или 8 кадров (4 ядра 8 потоков).

    Так что FPS порядка 80-400 вполне реально.

    Могу даже пример с батцать.
  • Sapersky (09.03.16 21:00) [2]
    Ускорит, если обработка не слишком простая, иначе может упереться в память. И если не будет зависимостей потоков друг от друга.

    В старом примере картинка разбивалась по вертикали на несколько полосок, каждый поток работал со своей полоской. Можно делать так, но есть более изящный метод, который позволяет прикручивать многопоточность почти к чему угодно с минимальными правками - parallel for. Заменяем внешний цикл обработки картинки

    for y:=0 to Dst.AbsHeight-1 do



    на что-то вроде

    Parallel.For(0, Dst.AbsHeight-1).Execute(
       procedure(y: integer)



    и всё, ну ещё некоторые переменные перенести в анонимную функцию, и убедиться, что итерации независимы друг от друга.

    Правда, нормальной библиотеки для этого дела я не нашёл. OTL почему-то даёт большие накладные расходы (на создание пула потоков?). Встроенный parallel for в XE7+ мне не понравился тем, что нельзя отключить многопоточность для отладки. Собственный велосипед толком не дописан, хотя могу выложить, если интересно.
    Опять же, Intel в своей IPP отказывается от многопоточности на низком уровне - утверждают, что неэффективно. Может, они и правы. Я толком не применял parallel for в рабочих проектах из-за пристрастия к старым версиям Дельфи, без анонимных функций не так красиво получается.
  • Dmk © (09.03.16 21:01) [3]
    >Dmk, ты ли это?
    Не работал я с потоками. Вот планирую заняться :(

    >1000 fps что-то не верится.
    Было-было. У меня GTX 980 и 6 ядер i7.

    На самом деле у меня масштабирование с использованием MMX. Почти все на ассемблере. Хотел сделать, чтобы другие ядра работали тоже. На больших файлах проседает производительность. Мне бы небольшой намек как в потоке обращаться к массиву, а там я сам доделаю. Думаю флаги какие то ставить надо, чтобы по 2-3 раза один и тот же участок памяти не читался.
  • Pavia © (09.03.16 21:14) [4]
    Какая у вас обработка?
  • Dmk © (09.03.16 21:38) [5]
    Есть такой цикл:

    for lpY := 0 to (Dest.qHeight - 1) do
    begin
     //Масштабируем 2 линии в одну
     FProcScaleScLines(lpSrc, lpDest, lpWidth, Src.qExtra.eScLen);
     //Следующие 2 скан-линии
     lpSrc := lpSrc + (Src.qExtra.eScLen shl 1);
     //Адрес следующей линии
     lpDest := lpDest + Dest.qExtra.eScLen;
    end;//for



    FProcScaleScLines - это перегружаемая процедура, которая масштабирует скан-линии.
    Вот этот цикл я и хотел в поток загнать. Только как сделать, чтобы в потоке разные скан-линии выбирались из памяти?
    procedure TImageStream.ScaleChannelScLines64A(Src, Dest, NumPixels, ScLen: uint64);
    asm
     mov r11, rsi
     mov r10, rdi
     push rbx

     mov rsi, src
     mov rdi, dest
     mov rdx, Self.FNumChannels
     mov rcx, NumPixels

     shr rcx, 1
     mov r8, ScLen
     mov r9, r8
     add r9, rdx

     xor ax, ax

    @@NextBytes:
     xor bx, bx
     mov al, [rsi]
     add bx, ax
     mov al, [rsi+rdx]
     add bx, ax
     mov al, [rsi+r8]
     add bx, ax
     mov al, [rsi+r9]
     add bx, ax
     shr bx, 2
     mov [rdi], bl

     add rdi, rdx
     add rsi, rdx
     add rsi, rdx

     dec rcx
     jnz @@NextBytes

     pop rbx
     mov rdi, r10
     mov rsi, r11
    end;

  • Dmk © (09.03.16 21:41) [6]
    Также есть процедуры Альфаблендинга и т.п. Хотел попробовать тоже в поток поместить. На одном потоке как то медленно получается.
  • Pavia © (09.03.16 21:44) [7]
    Создаешь N+1 поток.
    Босс + N рабочих.

    Даёшь задачу босу подсчитать сумму чисел в массиве. Босс делит весь массив на участки по числу рабочих. И отправляет сообщения рабочим. В сообщение указывается массив откуда начинать суммировать где заканчивать.

    К примеру
    первому рабочему картинка BMP, строки с 0 по 99
    второму рабочему картинка BMP, строки с 100 по 199
    ...
    восьмому и последнему рабочему картинка BMP, строки с 700 по 799

    Рабочий получает участок и начинает обрабатывать.

    Далее Босс ждёт пока все рабочие закончат работать. Как только все закончили бос радостно докладывает тому кто его вызвал что задача закончена.

          for i:=1 to Fabrick.Wokers.count-1 do
             begin
              cmd:=TSumData.Create;
              TSumData(cmd).Data:=TSumData(TasksList[0]).Data;
              TSumData(cmd).Count:=TSumData(TasksList[0]).Count;

              cmd.Count:=cmd.Count div Fabrick.Woker.Count; {cmd.Count -размер задачи}
              Fabrick.Wokers[i].SendCommand(cmd); {отправляем каждому рабочему свою команду}
             end;

         for i:=1 to Fabrick.Wokers.Count-1 do
              Fabrick.Wokers[i].Resume; {Пинаем рабочего, что-бы тот проснулся и начал делать работу}

          for i:=1 to Fabrick.Wokers.Count-1 do
              Fabrick.Wokers[i].WaitFor; {ждем пока рабочие закончат работать}


    Fabrick - тут у меня это пул потоков. Только мне бассейн не нужен. Я рабочих в чёрном теле держу.


    procedure TWoker.Execute;
    begin
    if Task<>nil then    
       Task.Exec;                   {Если есть задача выполняем}
     ReturnValue:=1;             {Нужно для WaitFor}
    end;

    procedure TSumData.Exec;
    var
     i:Integer;
     Massiv:PByte;
     Sum:Integer;
    begin
    // while not Terminated do {Добавить по желанию, чтобы ещё раз не делить задачу и не загромождать код не стал писать}
     Massiv:=Data;
     Sum:=0;
      for i := 0 to Count-1 do  {Для простоты, тут Count это тот который размер задачи, см код Босса }
       begin
         Sum:=Sum+Massiv^;
         Inc(Massiv);
       end;
     Self.Goal:=Self.Goal+Sum;  {Goal это у меня Result такой фирменный}
     inherited Exec;  { Вызываем предка что-бы он установил число обработанных байт Compiletion:=Count;}
    end;
  • Pavia © (09.03.16 21:58) [8]

    > Dmk ©   (09.03.16 21:38) [5]

    У вас смотрю Delphi новая там проще.
    Берём ваш цикл

    TParallel.For(0, Dest.qHeight - 1, procedure (lpY: Integer)

    begin
     //Масштабируем 2 линии в одну
     FProcScaleScLines(lpSrc, lpDest, lpWidth, Src.qExtra.eScLen);
     //Следующие 2 скан-линии
     lpSrc := lpSrc + (Src.qExtra.eScLen shl 1);
     //Адрес следующей линии
     lpDest := lpDest + Dest.qExtra.eScLen;
    end);



    Тут дефи сам из
    procedure (lpY: Integer)  begin ...

    создаёт объект и метод внутри него.
  • Pavia © (09.03.16 21:59) [9]
    Официальный пример от эмбаркодеров на параллельный цикл
    http://docwiki.embarcadero.com/RADStudio/Seattle/en/Using_TParallel.For_from_the_Parallel_Programming_Library
  • Dmk © (09.03.16 22:20) [10]
    >У вас смотрю Delphi новая там проще.
    Не, это в XE10. У меня XE6. В ней нет поддержки TParallel.
  • Sapersky (10.03.16 04:43) [11]
    Оно не встроено в компилятор, просто библиотечная функция. Можно и самому написать:
    https://drive.google.com/file/d/0B_vHqwd58bsUcEprOG5BaTBRVW8/view?usp=sharing

    > Pavia ©   (09.03.16 21:58) [8]

    Только указатели нужно считать в зависимости от lpY, а не инкрементом.
  • Pavia © (10.03.16 06:12) [12]
    Да вчера видимо уже спал. Конечно не инкриментом. А непосредственное от lpY.
     
      lpSrc := lpY* (Src.qExtra.eScLen shl 1);
     //Адрес следующей линии
     lpDest := lpY* Dest.qExtra.eScLen;
  • Eraser © (11.03.16 03:49) [13]

    > Dmk ©   (09.03.16 18:18) 

    возможно имеет смысл всеми способами постараться переложить работу на GPU. уверен, что прирост производительности будет просто огромный, если грамотно реализовать.
  • Dmk © (11.03.16 11:21) [14]
    Eraser ©   (11.03.16 03:49) [13]
    Имеется ввиду OpenGL, DirectX и т.п.?
  • Dmk © (11.03.16 12:41) [15]
    Eraser ©   (11.03.16 03:49) [13]
    Все бы замечательно, если бы OpenGL или DirectX считали матрицы.
    Это бы мне помогло.

    что-то типа этого:
     movq xmm4, [_x]
     movq xmm5, [_y]
     movq xmm6, [_z]

     //в xmm7 занесем 256 для умножения
     movq xmm7, _mulByte

     pxor xmm0, xmm0

     mov rdi, XYZRGB_addr
     movq xmm1, [rdi]
     movq xmm2, [rdi + 8]
     movq xmm3, [rdi + 16]

     mulpd xmm1, xmm4
     mulpd xmm2, xmm5
     mulpd xmm3, xmm6

     addpd xmm0, xmm1
     addpd xmm0, xmm2
     addpd xmm0, xmm3

     mulpd xmm0, xmm7
     movq [_x], xmm0

  • Dmk © (11.03.16 12:42) [16]
    Или экспоненту с логарифмом считали бы быстрее. Проц тормозит :(
  • Sapersky (11.03.16 14:53) [17]
    Всё они могут посчитать.
    Другой вопрос, что при однократной обработке картинки пересылка в видеокарту и обратно может всё затормозить.
    Распиши подробнее, что делаешь - а то как-то всё обрывками, начали с масштабирования и блендинга, закончили логарифмами.
  • Eraser © (11.03.16 16:38) [18]

    > Dmk ©   (11.03.16 11:21) [14]

    я редко на практике сталкивался с DX и OpenGL, однако, все таки сталкивался.
    На сколько я успел заметить, работа с матрицами, это один из краеугольных моментов данных технологий.

    Но я имел ввиду другое, возможно, надо переложить всю работу по требуемой обработке изображения на эти технологии. Т.е. написать какой-то шейдер и пусть оно само там производит нужные преобразования.

    Ну или что-то вроде этого https://msdn.microsoft.com/ru-ru/library/windows/desktop/ff684172(v=vs.85).aspx
  • Pavia © (11.03.16 18:11) [19]

    > Или экспоненту с логарифмом считали бы быстрее. Проц тормозит
    > :(

    Логарифм в ядре считает 1 блок. Если вы распараллеливании при помощи SIMD может несколько. А вот в видео карте таких блоков много.
    И для экспоненты тоже.
    Вот только это ещё от видео карты зависит.


    > Все бы замечательно, если бы OpenGL или DirectX считали
    > матрицы.

    Они и считают. Еще моя Geforce4MX так умела.

    Но нет смысла использовать OpenGL и DirectX.
    Если видео карта NVidia то надо использовать CUDA.
    Если видео карта ATI то надо использовать ATI Firmvare - сейчас это OpenCL
    Вот под Intel надо смотреть что быстрее шредер или OpenCL
 
Конференция "Media" » Доступ к блоку памяти из TThread
Есть новые Нет новых   [118241   +25][b:0][p:0.003]