- 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 하는 것도 가능할까??