- inside vm
- vm.js
- 성능??
- 데모
-
vm의 테스트 코드
-
다른 모듈(
repl
,tty
) 과의 관련?? -
eval의 치명적인 단점은 코드 최적화가 이루어지지 않는다는 점이다.
- 자바 스크립트 코드조각, 파일을 런타임에 실행
$ node filename.js
시filename.js
의 내용을 로드해 실행할 때week_2.md
의module load
과정 분석 참고
vm.js는 module.js 와 같이 evals
라는 네이티브 모듈을 통해 스크립트를 실행한다.
var Script = process.binding('evals').NodeScript;
var runInThisContext = Script.runInThisContext;
context
- 하나의 v8 인스턴스에서 독립적인 자바스크립트 실행하기 위한 실행 환경
- v8 에서 자바스크립트를 실행하기 위해서는 반드시 필요하다.
sandbox
- v8은 보안상의 이슈로
context
생성시 만들어지는global
객체를 바로 컨트롤 할 수 없다. - 대신
vm
의 메소드로 전달되는sandbox
안지를 통해 간접적으로global
처럼 사용할 수 있다. sandbox
의 프로퍼티 실행하려는 스크립트와 바로 공유되지 않고,context
로 복사되었다가 스크립트가 끝나면 다시 원래 객체로 복사된다.
문자열을 자바스크립트로 해석하고, 이를 평가한 결과값으로 출력하는 자바스크립트 전역함수.
Davide Flanagan. 《 JavaScript The Definitive Guide 6/E 》. Insight. 105쪽. ISBN 978-89-6626-068-3
gdb
로 디버깅
$ gdb -d ~/Documents/workspace/node_project/node/src/ --args node vm_study.js
(gdb) b node.cc:Binding
(gdb) run
(gdb) p *modp
$17 = {
version = 1,
dso_handle = 0x0,
filename = 0x10039a2f4 "../src/node_script.cc",
register_func = 0x100015ffb <node::InitEvals(v8::Handle<v8::Object>)>,
modname = 0x10039a30a "node_evals"
}
(gdb) b node_script.cc:443
참고로 node.cc:Binding 은 node 초기 시작과정에 evals
외에도 natives
, buffer
, fs
, constants
, tty_wrap
, timer_wrap
, cares_wrap
, signal_watcher
, crypto
모듈을 바인딩 한다.
- 자바스크립트 객체를 생성하고 생성 정보를 캐시
process.binding('evals') 이 호출되면,
- Binding이 호출되고,
- get_builtin_module은
node_module_list
에서 eval과 관련된 구조체를 리턴해 준다. 여기에 eval 모듈과 관련된 초기화 함수가 지정되어 있다. (위의 modp 참고)- 이후 해당 구조체에서
register_function
을 호출하고, 캐쉬에 저장 exports
객체를 넘겨주는데 이거 뭐임?node_extensions.cc:node_module_list
는 누가 세팅해줌???
- 이후 해당 구조체에서
register_function
에 지정된 InitEvals 가 호출되면,- WrappedScript::Initialize 를 통해
NodeScript
심볼을 생성한다.(오브젝트를 만든다?)
결국 evals
를 바인딩하고 NodeScript
인스턴스를 통해 메소드를 호출하면, node_script.cc
의 메소드들이 호출된다.
process.binding
은 addon
에 사용되는 process.dlopen
과는 다르게 이미 초기화 함수의 위치를 알고 시작한다.
vm.js의 메소드들(createContext, runInContext, runInThisContext, runInNewContext)은 결국 node_script.cc
의 WrappedScript 클래스에 존재하는 동명의 메소드들과 연결된다.
이 메소드들을 살펴보면 모두 WrappedScript::EvalMachine
메소드를 호출한다.
gdb로 살펴보면
$ gdb -d ~/Documents/workspace/node_project/node/src/ --args node vm_study.js
(gdb) b node_script.cc:317
(gdb) run
EvalMachine 함수의 큰 흐름은 다음과 같다. (runInNewContext
경우)
args
에서 코드, 파일이름 추출context
생성 하고- 전달받은
sandbox
의 프로퍼티를context
에 복사한다.CloneObject
- V8엔진의 Script 인스턴스를 만든뒤
- Script->run() 을 통해 스크립트 실행
- 실행이 성공하면,
context
의 프로퍼티을sandbox
에 복사한다.
ControlFlowGraph-EvalMachine.svg
이미지 참고
즉, sandbox 객체의 내용은 공유되지 않고 복사된다.
이 흐름은 여기 에 좀 더 간단하게 설명되어 있다.
여기 에 선언되어 있음
global
객체의 템플랫을 생성- access 권한 설정
- CreateEnvironment 를 통해 Genesis 메소드에서에서 실제
global
객체를 생성한다.
https://github.com/joyent/node/blob/v0.8.18-release/src/node_script.cc#L104
- 자바스크립트 문자열(siaf)을 만들고 컴파일 한뒤,
- 해당 자바 스크립트 문자열을 실행한다.
- 자바스크립트 문자열 내부에서는
source
의 프로퍼티를target
에 정의한다.
- 자바스크립트 문자열 내부에서는
왜 이놈은 자바스크립트 코드로 넣어놨지?? ㄷ
결국 스크립트 코드를 받아 실행을 하는 클래스는 Script 클래스 이다.
Script:Compile
메소드의 구현은 api.cc 에서 찾을 수 있다.
Script 함수의 역할은 새로 읽는 파일의 함수 호출시 this
키워드가 ns
를 가리키도록 하는 것이다.
return ns[f].apply(ns, arguments);
createScript에서 ctx를 어떻게 생략하고 호출할 수 있지?
vm.runInThisContext(code, [filename])
- 전달된
code
를 컴파일하고 실행한다. 실행시 로컬 스코프에 엑세스 할 수 없다. - 외부와 데이터 교환이 없는 단독코드를 실행할 때. (가장 빠르다.)
vm.runInNewContext(code, [sandbox], [filename])
- 새로운
context
를 만들고,sandbox
를global
로code
를 실행한다. sandbox
는code
가 실행되기 전에context
에 복사되었다가, 실행이 끝나면 다시 로컬 스코프의sandbox
로 복사된다.code
실행시 로컬 스코프의 내용을 반영하고 싶거나,code
의 수행결과를 로컬 스코프로 가져오고 싶은 경우sandbox
의 프로퍼티가 많으면 많을수록 느려진다.
vm.runInContext
- 이미 만들어진
context
를 통해code
를 실행한다. vm.createContext
와 묶여서 실행됨.- 동일한
context
를 여러번 재활용 하고자 할 때.
vm.createScript(code)
- 전달된
code
를 컴파일만 한다. 코드는 나중에 실행시킬 수 있다.
script.runInThisContext()
- script를 현재 컨텍스트에서 실행한다. 실행시 로컬 스코프에 엑세스 할 수 없다.
script.runInNewContext([sandbox])
sandbox
를global
로 코드를 실행한다.
아래의 코드를 통해 node의 global
인스턴스를 포함한 context
를 만들고 코드를 실행할 수 있다.
var target = {};
Object.getOwnPropertyNames(global).forEach(function(key) {
var desc = Object.getOwnPropertyDescriptor(global,key);
Object.defineProperty(target, key, desc);
});
context = vm.createContext(target);
vm.runInContext("console.log('hello');", context);
C++ 내부코드에서 컴파일 함수가 호출되는 것으로 보아 v8 엔진의 코드 최적화가 이루어 지고 있는 듯한데,
Google I/O 2012 - Breaking the JavaScript Speed Limit with V8 에서 사용된 prime.js
로 테스트 해 보았음
25000 번째 소수를 찾는 코드. v8 이 최적화 할 수 있는 부분이 포함되어 있다.
최적화 되기 어려운 코드를 실행하면
$ time node prime_no_opt.js
287107
real 0m13.706s
user 0m13.604s
sys 0m0.090s
최적화 과정을 거친 코드를 실행하면
$ time node prime.js
287107
real 0m0.091s
user 0m0.074s
sys 0m0.015s
vm 을 통해서 실행해 보면, 응? 차이가 거의 없네?? v8의 성능은 제대로 발휘되고 있는 듯 하다.
$ time node prime_vm.js
287107
real 0m0.092s
user 0m0.075s
sys 0m0.016s
자 bench.sh
를 돌려보자, sandbox
와 context
는 아래와 같이 적용
var sandbox = global;
var context = vm.createContext(global);
결과는 다음과 같다.
vm - runInThisContext : 33ms
vm - runInNewContext : 52ms
vm - runInContext : 45ms
script - runInThisContext : 30ms
script - runInNewContext : 46ms
runInThisContext
가 빠른 것은 당연한 듯하다.CloneObject
가 호출되지 않기 때문이다.runInContext
는CloneObject
호출 횟수가 줄어runInNewContext
보다 빠르다.script.runInThisContext
의 경우 미리 컴파일된 스크립트를 사용하기에, 대부분의 경우vm.runInThisContext
보다 (아주 근소한 차이로) 빠르다. 컴파일 시간이 오래 걸리는 코드라면 속도의 차이가 크게 날 듯 하다.
- codeschool 의 javascript 에디터
- module.js
______________
< demo time !! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
see demo app on heroku!!
신기하게도 bench_wierd.js
의 마지막 bench
메소드의 호출 순서를 바꾸면 결과가 다르다. 왜일까?
- gc??
- 클래스의 재활용??
컨텍스트 자체를 serialize 하는 것도 가능할까??