삽질하는플머

LuaJit_Research

탐구생활/Lua
2010. 2. 2
-------------

맵서버 전체적으로 루아가 사용되면서 일본 서버에서는 한 번도 나타난 적 없는 길드창고 복사버그 등이 발생할 수 있다는 것이 확인되었다.
캐릭터가 틱을 갖게 되는 시간이 벌어지는 것이 원인으로 보이는데... 안전장치는 논외로 하더라도 근본적으로 루아의 성능문제를 고민할 시점이라는 이야기...

LuaJit 을 희망삼아 삽질을 시작 해 보자. 먼저 http://luajit.org/ 에서 안정버전인 LuaJIT-1.1.5 를 내려받는다.
이 버전은 현재 사용중인 Lua 5.1.4 를 기반으로 한 것이다.

간만에 빌드하는 것이라 기억이 가물가물하네...

루트의 Makefile 은 인스톨에 대한 부분을, src의 Makefile 은 타겟에 대한 정보를 담고 있다.
mingw 도 들어있으니 한 번 시도.



MSYS 에서 make mingw 타이핑. 별로 고민할 것도 없이 lua51.dll 과 luajit.exe 가 생성된다.
생성된 DLL은 258kb 로, 루아 5.1.4 의 152kb 에 비해 100kb 정도 늘어나있다. -Os 옵션을 주고 컴파일하면 루아 코어에 32kb 만 증가된다고 하던데...

src/Makefile 내의 CFLAG 를 -O3 를 -Os 로 변경하니 용량이 170kb 로 줄어든다. 캬~~
지금은 성능이 문제니, 원래대로 -O3 로 해 둔다.

컴파일된 DLL에는 예전에 못보던 함수 몇 개가 더 보인다.


Exports from lua51.dll
  127 exported name(s), 127 export addresse(s).  Ordinal base is 1.
  Sorted by Name:
    RVA      Ord. Hint Name
    -------- ---- ---- ----
    0001E480    1 0000 luaJIT_compile
    0001E690    2 0001 luaJIT_setmode
    0001E870    3 0002 luaJIT_version_1_1_5
........
    00033B40  122 0079 luaopen_jit
........


아마도 이 물건들이 JIT 을 구동하기 위한 함수들인 것으로 보인다.




DLL에 버전 정보가 없으니 매우 썰렁한 느낌이 드는군. 적당한 버전 정보를 추가 해 보세~~
조금 어거지로 끼워맞춘 감이 있지만... 뭐 되기만 하면 되지.

일단 원본 Makefile 은 백업한다.

lua_dll_win32.rc 파일을 만들고 내용은 다음과 같이 채운다.

1 VERSIONINFO
 FILEVERSION 2010,2,2,1
 PRODUCTVERSION 2010,2,2,1
 {
  BLOCK "StringFileInfo"
  {
   BLOCK "040904E4"
   {
    VALUE "Comments", "LuaJIT 1.1.5 based on Lua 5.1.4\000"
    VALUE "CompanyName", "\000"
    VALUE "FileDescription", "LuaJIT 1.1.5\000"
    VALUE "FileVersion", "2010.2.2.1\000"
    VALUE "InternalName", "\000"
    VALUE "LegalCopyright", "Compiled by ORANKE\000"
    VALUE "LegalTrademarks", "\000"
    VALUE "OriginalFilename", "lua51.dll\000"
    VALUE "ProductName", "\000"
    VALUE "ProductVersion", "2010.2.2.1\000"
   }
  }
  BLOCK "VarFileInfo"
  {
   VALUE "Translation", 0x0409, 0x04E4
  }
 }


 Makefile 의 mingw: 부 상단에 다음과 같이 리소스 컴파일 코드를 기록한다. (154 라인 근처)

lua_dll_win32_res:
 $(RC) -i lua_dll_win32.rc -J rc -o lua_dll_win32_res.o -O coff


mingw: 부에 다음과 같이 리소스 컴파일 코드를 넣는다.

mingw:
 $(MAKE) lua_dll_win32_res a "MYCFLAGS=-DLUA_BUILD_AS_DLL"
 $(MAKE) "LUA_A=lua51.dll" "LUA_T=luajit.exe" \
 "AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \
 "MYCFLAGS=-DLUA_BUILD_AS_DLL -maccumulate-outgoing-args" \
 "MYLIBS=" "MYLDFLAGS=-s" luajit.exe



$(LUA_A): 부분에서 위에서 만들어진 lua_dll_win32_res.o 가 추가되도록 해준다.

$(LUA_A): $(CORE_O) $(LIB_O) lua_dll_win32_res.o
 $(AR) $@ $?
 $(RANLIB) $@


이제 컴파일한 결과는 다음과 같다. 






추가로, mingw: 부의 마지막줄에 다음을 추가하면, DLL 의존성 없는 luac.exe 파일을 얻을 수 있다.  

$(MAKE) "LUAC_T=luac.exe" luac.exe





이제 실제로 속도를 테스트 해 보자. 기존 루아 5.1.4 와 분리하기 위해 dll 명칭은 lua51.dll 을 그대로 사용하자. 

루아 환경 생성시 luaopen_jit() 로 JIT 라이브러리를 로드하고 luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE or LUAJIT_MODE_ON) 로 JIT 을 켜준다. 

컴파일되지 않은 루아파일 중 덩치가 큰 0번의 경우, 이 경우 130APS -> 96APS 로 속도가 줄어든다.

-s 를 넣어 디버깅정보를 털어낸 컴파일된 루아파일의 경우 130APS -> 100APS 정도가 된다. 

의외로... 컴파일된 루아파일의 실행속도가 오래전에 테스트 했을 때와 조금 다르다... 횟수를 여러번 돌려서 측정해봐야 할 듯...



속도를 APS (Action per Second) 단위로 측정하기 위해 내부에서 QueryPerformanceCounter() 를 호출하는 Lua.InitPerfValue(), Lua.GetPerfValue() 글루함수를 추가하고 다음 코드를 실행시켜 보았다. 

Lua.InitPerfValue();

j = 0;
for i=1, 10000000, 1 do
  j = j + i;
end;

print('------------');
print('Value', j);
print('APS', Lua.GetPerfValue());


결과는 다음과 같다. 먼저 LuaJIT 을 사용하지 않은 상태.




사용한 경우. 




단순한 루프의 경우, 약 3.3 배의 속도향상을 보인다. 


현재 맵서버는 여러 이벤트에서 컴파일된 루아파일을 숫자아이디로 구분해 호출하는 방식도 사용하고 있으므로, luaL_loadbuffer() 의 수행속도도 체크 해 보자. 현재 이 처리는 Lua.ExecLuaScript() 라는 글루함수가 처리하고 있다. 이 함수를 여러 번 반복호출하여 성능을 알아보자. 

Lua.InitPerfValue();

for i=1, 100, 1 do
  Lua.ExecLuaScript(0);
end;

print('------------');
print('APS', Lua.GetPerfValue());



컴파일 되지 않은 날 루아파일의 경우 

LuaJIT 비사용 : 1.37~1.40 APS.
LuaJIT 사용 :  1.13~1.14 APS


컴파일된 루아파일의 경우

LuaJIT 비사용 : 1.34~1.42 APS.
LuaJIT 사용 :  1.08~1.13 APS


-s 옵션으로 컴파일된 루아파일의 경우

LuaJIT 비사용 : 1.39~1.45 APS.
LuaJIT 사용 :  1.08~1.11 APS


LuaJIT 의 성능이 전반적으로 30% 가량 낮게 나오고, 오히려 컴파일된 경우 그 차이는 더욱 커지게 된다. 
기존에 적재된 함수를 사용하는 경우, 즉 플레이어의 밸런스 처리 등에서는 속도향상이 있지만, 이벤트에 따른 루아 호출에서는 오히려 속도감소가 발생할 수 있다는 이야기. 불러온 뒤 JIT처리를 하는 부분에 상당한 로드가 걸린다는 것을 알 수 있다. 

JIT 으로 처리된 상태를 그대로 저장할 수 있다면 속도를 올리는 데 많은 도움이 될텐데... 흠냐리...