luaでもマルチスレッドやらせろ！！！

　
この記事は[アドカレ](https://qiita.com/advent-calendar/2022/lua)に参加しています。

# luaでもマルチスレッドやらせろ！！！
先日、[このような記事](https://qiita.com/metaphysical_bard/items/ff219a34ffee78ab6f71)を書きました。luaでも外部ライブラリを入れることでマルチスレッドできるよ～みたいなことの事前調査みたいなものです。

さて、luaでもマルチスレッドできると分かったので、試してみました。

ところが、外部ライブラリさんがエラーばかり吐いて全く機能してくれない…。

じゃあ、違うものを、ということで別の外部ライブラリを試してみました。が、こちらも動かない…。

…というのを三回ぐらい繰り返したので、自分で実装することにしました。

## 実装方法
実装方法は[いくつか方法](https://stackoverflow.com/questions/26574705/lua-newstate-vs-lua-newthread)があるみたいです。

今回はお試しということで、スレッドごとにlua環境を用意してやることにしました。ここで問題になるのが、変数の共有ですが、ffiでポインタを受け渡すようにしました。とっても理知的な反面、手間がかかります。~~この時点で没案行き~~

## dllの実装
なんだかんだでマルチスレッドができるdllを作成することができました。
[自作のコミュニティ](https://twitter.com/metaphysicalba1/status/1521523370059173890)にコードとバイナリがありますので、よければ。

ところで、個人のメモ帳のことをコミュニティと偽るのは大丈夫なのだろうか。

## 性能実験
気になるのはその性能ですね。
マルチスレッドにすることで、どれだけ速度が良くなるのか。
以下のコードで比較してみました。
(以下のコードはざっくり計測を目標としているので、正確な結果を返しません。予めご了承ください。)
```lua
--[[

blur.anm
	マルチスレッドの性能テスト。

]]

--track0:size,1,50,10,1
--track1:type,1,2,1,1

local t1,t2
local d=require("M_Blur_Module")
t1=d.time()--計測開始

if(obj.track1==1)then
---------------------------------------------
---------------------------------------------

local ffi=require"ffi"

pcall(ffi.cdef,[[
    typedef struct Pixel_ {
      uint8_t b,g,r,a;
    } Pixel;
  ]])

local work,data,w,h=obj.getpixeldata"work",obj.getpixeldata()
local c=ffi.cast("Pixel*",data)
local r=ffi.cast("Pixel*",work)

local function f(a)--0~255の範囲で四捨五入する関数。
 if(a<0)then return 0
 elseif(a>255)then return 255
 else
  return math.floor(a+0.5)
 end
end

local t0=obj.track0/3
local ha=math.floor(obj.track0)
local ha2=ha*2+1
local ta={}
local total_ta=0
for i=1,ha2 do--taにガウス関数で重みを付ける
 local s=(i-1)-ha
 ta[i]=math.exp(-(s*s)/(2*t0*t0))/(math.sqrt(2*math.pi)*t0)
 total_ta=total_ta+ta[i]
end

for i=1,ha2 do--taの合計値が1.0になるようにする
 ta[i]=ta[i]/total_ta
end

local qt={}--ぼかす際の変数を入れる
local af=ffi.new("Pixel",{0})

--横方向
for y=0,h-1 do
 local ix=y*w
 local s=-ha

 for i=1,ha2 do
  if(s>=0 and s<w)then
   qt[i]=c[ix+s]
  else
   qt[i]=af
  end
  s=s+1
 end

 for x=0,w-1 do
  local to={0,0,0,0}

  for i=1,ha2 do
   to[1]=to[1]+qt[i].r*ta[i]
   to[2]=to[2]+qt[i].g*ta[i]
   to[3]=to[3]+qt[i].b*ta[i]
   to[4]=to[4]+qt[i].a*ta[i]
  end

  r[ix].r=f(to[1])
  r[ix].g=f(to[2])
  r[ix].b=f(to[3])
  r[ix].a=f(to[4])

  for i=1,ha2-1 do
   qt[i]=qt[i+1]
  end

  if(x+ha+1<w)then
   qt[ha2]=c[ix+ha+1]
  else qt[ha2]=af end
  ix=ix+1
 end
end

--縦方向
for x=0,w-1 do
 local ix=x
 local s=-ha

 for i=1,ha2 do
  if(s>=0 and s<h)then
   qt[i]=r[ix+s*w]
  else
   qt[i]=af
  end
  s=s+1
 end

 for y=0,h-1 do
  local s=-ha
  local to={0,0,0,0}

  for i=1,ha2 do
   to[1]=to[1]+qt[i].r*ta[i]
   to[2]=to[2]+qt[i].g*ta[i]
   to[3]=to[3]+qt[i].b*ta[i]
   to[4]=to[4]+qt[i].a*ta[i]
  end

  c[ix].r=f(to[1])
  c[ix].g=f(to[2])
  c[ix].b=f(to[3])
  c[ix].a=f(to[4])

  for i=1,ha2-1 do
   qt[i]=qt[i+1]
  end

  if(y+ha+1<h)then
   qt[ha2]=r[ix+(ha+1)*w]
  else qt[ha2]=af end
  ix=ix+w
 end
end

obj.putpixeldata(data)

---------------------------------------------
---------------------------------------------

else

---------------------------------------------
---------------------------------------------

local code=[==[

local ffi=require"ffi"

pcall(ffi.cdef,[[
    typedef struct Pixel_ {
      uint8_t b,g,r,a;
    } Pixel;

    typedef struct SETSCRIPTDATA_ {
	int th_num;
      void* data;
	void* work;
	int w;
	int h;
	int track;
    } SETSCRIPTDATA;
  ]])

local THDATA=ffi.cast("SETSCRIPTDATA*",P)

THDATA.th_num=I
local c=ffi.cast("Pixel*",THDATA.data)
local r=ffi.cast("Pixel*",THDATA.work)
local w,h=THDATA.w,THDATA.h
local track0=THDATA.track

local function f(a)--0~255の範囲で四捨五入する関数。
 if(a<0)then return 0
 elseif(a>255)then return 255
 else
  return math.floor(a+0.5)
 end
end

local t0=track0/3
local ha=math.floor(track0)
local ha2=ha*2+1
local ta={}
local total_ta=0
for i=1,ha2 do--taにガウス関数で重みを付ける
 local s=(i-1)-ha
 ta[i]=math.exp(-(s*s)/(2*t0*t0))/(math.sqrt(2*math.pi)*t0)
 total_ta=total_ta+ta[i]
end

for i=1,ha2 do--taの合計値が1.0になるようにする
 ta[i]=ta[i]/total_ta
end

local qt={}--ぼかす際の変数を入れる
local af=ffi.new("Pixel",{0})

local start_h=math.floor(h/N)*I
local h_amari=h%N
local finish_h=start_h+math.floor(h/N)
if(I==N-1)then finish_h=finish_h+h_amari end

--横方向
for y=start_h,finish_h-1 do
 local ix=y*w
 local s=-ha

 for i=1,ha2 do
  if(s>=0 and s<w)then
   qt[i]=c[ix+s]
  else
   qt[i]=af
  end
  s=s+1
 end

 for x=0,w-1 do
  local to={0,0,0,0}

  for i=1,ha2 do
   to[1]=to[1]+qt[i].r*ta[i]
   to[2]=to[2]+qt[i].g*ta[i]
   to[3]=to[3]+qt[i].b*ta[i]
   to[4]=to[4]+qt[i].a*ta[i]
  end

  r[ix].r=f(to[1])
  r[ix].g=f(to[2])
  r[ix].b=f(to[3])
  r[ix].a=f(to[4])

  for i=1,ha2-1 do
   qt[i]=qt[i+1]
  end

  if(x+ha+1<w)then
   qt[ha2]=c[ix+ha+1]
  else qt[ha2]=af end
  ix=ix+1
 end
end

local start_w=math.floor(w/N)*I
local w_amari=w%N
local finish_w=start_w+math.floor(w/N)
if(I==N-1)then finish_w=finish_w+w_amari end

--縦方向
for x=start_w,finish_w-1 do
 local ix=x
 local s=-ha

 for i=1,ha2 do
  if(s>=0 and s<h)then
   qt[i]=r[ix+s*w]
  else
   qt[i]=af
  end
  s=s+1
 end

 for y=0,h-1 do
  local s=-ha
  local to={0,0,0,0}

  for i=1,ha2 do
   to[1]=to[1]+qt[i].r*ta[i]
   to[2]=to[2]+qt[i].g*ta[i]
   to[3]=to[3]+qt[i].b*ta[i]
   to[4]=to[4]+qt[i].a*ta[i]
  end

  c[ix].r=f(to[1])
  c[ix].g=f(to[2])
  c[ix].b=f(to[3])
  c[ix].a=f(to[4])

  for i=1,ha2-1 do
   qt[i]=qt[i+1]
  end

  if(y+ha+1<h)then
   qt[ha2]=r[ix+(ha+1)*w]
  else qt[ha2]=af end
  ix=ix+w
 end
end

]==]

local ffi=require"ffi"
local d=require"M_threads_Module"

local work,data,w,h=obj.getpixeldata"work",obj.getpixeldata()

pcall(ffi.cdef,[[
    typedef struct SETSCRIPTDATA_ {
	int th_num;
      void* data;
	void* work;
	int w;
	int h;
	int track;
    } SETSCRIPTDATA;
  ]])

local thread_n=math.max(1,d.s())
local a={}
local ap={}

for i=1,thread_n do
a[i]=ffi.new("SETSCRIPTDATA[1]")
a[i][0].data=data
a[i][0].work=work
a[i][0].w=w
a[i][0].h=h
a[i][0].track=obj.track0
ap[i]=a[i]
end

local er=d.c(thread_n,code,ap)

for i=1,thread_n do
local num=tonumber(a[i][0].th_num)
--debug_print(string.format("thread_num:%d,error:%d",num,er[num+1]))
end

obj.putpixeldata(data)

---------------------------------------------
---------------------------------------------
end


t2=d.time()--計測終了
local TIME=t2-t1

local data,w,h=obj.getpixeldata()

debug_print(string.format("%f[ms],w=%5f,h=%5f",TIME,w,h))
obj.draw()

```

## 結果
処理時間、画像の横幅、画像の縦幅の順に結果を出力しています。

まずはマルチスレッドなしver.
```
[23:56:54]      10.666900[ms],w=192.000000,h=108.000000
[23:56:58]      23.735600[ms],w=283.000000,h=159.000000
[23:56:59]      35.898300[ms],w=374.000000,h=210.000000
[23:56:59]      55.846200[ms],w=465.000000,h=261.000000
[23:56:59]      78.312600[ms],w=556.000000,h=313.000000
[23:56:59]      115.052900[ms],w=647.000000,h=364.000000
[23:56:59]      139.841100[ms],w=738.000000,h=415.000000
[23:56:59]      173.765900[ms],w=828.000000,h=466.000000
[23:57:00]      220.490800[ms],w=919.000000,h=517.000000
[23:57:00]      269.360700[ms],w=1010.000000,h=568.000000
[23:57:00]      312.319700[ms],w=1101.000000,h=619.000000
[23:57:01]      373.860800[ms],w=1192.000000,h=671.000000
[23:57:01]      425.744100[ms],w=1283.000000,h=722.000000
[23:57:02]      487.071700[ms],w=1374.000000,h=773.000000
[23:57:02]      551.982500[ms],w=1465.000000,h=824.000000
[23:57:03]      623.929800[ms],w=1556.000000,h=875.000000
[23:57:04]      691.688200[ms],w=1647.000000,h=926.000000
[23:57:04]      768.495500[ms],w=1738.000000,h=978.000000
[23:57:05]      853.336300[ms],w=1829.000000,h=1029.000000
[23:57:07]      2139.830600[ms],w=1920.000000,h=1080.000000
```

次にマルチスレッドver.
```
[23:59:08]      9.241500[ms],w=192.000000,h=108.000000
[23:59:11]      12.451600[ms],w=283.000000,h=159.000000
[23:59:12]      15.730300[ms],w=374.000000,h=210.000000
[23:59:12]      33.719700[ms],w=465.000000,h=261.000000
[23:59:12]      43.787800[ms],w=556.000000,h=313.000000
[23:59:12]      49.983100[ms],w=647.000000,h=364.000000
[23:59:12]      61.831800[ms],w=738.000000,h=415.000000
[23:59:12]      73.344900[ms],w=828.000000,h=466.000000
[23:59:12]      91.863800[ms],w=919.000000,h=517.000000
[23:59:12]      108.050100[ms],w=1010.000000,h=568.000000
[23:59:13]      124.576600[ms],w=1101.000000,h=619.000000
[23:59:13]      147.918700[ms],w=1192.000000,h=671.000000
[23:59:13]      164.971000[ms],w=1283.000000,h=722.000000
[23:59:13]      125.039100[ms],w=1374.000000,h=773.000000
[23:59:13]      220.602900[ms],w=1465.000000,h=824.000000
[23:59:13]      160.000400[ms],w=1556.000000,h=875.000000
[23:59:14]      225.907000[ms],w=1647.000000,h=926.000000
[23:59:14]      203.006700[ms],w=1738.000000,h=978.000000
[23:59:14]      232.062900[ms],w=1829.000000,h=1029.000000
[23:59:15]      265.403500[ms],w=1920.000000,h=1080.000000
```

ちゃんと処理速度が速くなっていて安心ですね。

## おわりに
luaでマルチスレッドやってみました。
変数共有とデバッグが大変ですが、処理速度の向上が見込めますね。
次はコルーチンでやる方法を試してみたいです。