일반적으로 어휘 바인딩과 동적 바인딩
다음 예제를 고려하십시오.
(let ((lexical-binding nil))
(disassemble
(byte-compile (lambda ()
(let ((foo 10))
(message foo))))))
lambda
로컬 변수를 사용 하여 단순 을 컴파일하고 즉시 분해 합니다. 함께 lexical-binding
장애인, 위와 같이, 바이트 코드의 모습은 다음과 같습니다 :
0 constant 10
1 varbind foo
2 constant message
3 varref foo
4 call 1
5 unbind 1
6 return
varbind
및 varref
지침을 참고하십시오 . 이러한 명령어 는 힙 메모리 의 전역 바인딩 환경 에서 해당 변수 를 이름별로 바인드하고 조회 합니다 . 이 모든 것은 성능에 악영향을 미칩니다. 문자열 해싱 및 비교 , 전역 데이터 액세스를위한 동기화 및 CPU 캐싱에서 좋지 않은 반복 힙 메모리 액세스가 포함됩니다. 또한 동적 변수 바인딩 은의 끝에서 이전 변수 로 복원 해야하므로 바인딩이있는 각 블록에 대한 추가 조회 가 추가 됩니다.let
n
let
n
만약 당신 바인드 lexical-binding
에 t
위의 예에서 바이트 코드는 다소 다른 모습 :
0 constant 10
1 constant message
2 stack-ref 1
3 call 1
4 return
참고 그 varbind
와 varref
완전히 사라졌다. 로컬 변수는 단순히 스택으로 푸시되고 stack-ref
명령을 통해 상수 오프셋으로 참조됩니다. 본질적으로 변수는 상수 시간 , 스택 내 메모리 읽기 및 쓰기 로 바인딩되고 읽 히며, 이는 완전히 로컬이므로 동시성 및 CPU 캐싱과 잘 작동하며 문자열을 전혀 포함하지 않습니다.
일반적으로, 로컬 변수의 사전 조회 바인딩 (예로 let
, setq
등)가 훨씬 적은 메모리 및 실행 복잡성 .
이 특정 예
동적 바인딩을 사용하면 위와 같은 이유로 각각의 성능이 저하됩니다. 더 많은 변수가 동적 변수 바인딩을 허용합니다.
특히, 추가로 let
내의 loop
본체 바운드 변수 복원 할 필요가 루프의 각 반복 가산, 각 반복에 추가 변수 조회 . 따라서 전체 루프가 끝난 후 반복 변수가 한 번만 재설정되도록 루프 본문을 내보내는 것이 더 빠릅니다 . 그러나 반복 변수가 실제로 필요하기 전에 바인딩되어 있기 때문에 이것은 특히 우아하지 않습니다.
어휘 바인딩을 사용하면 let
s가 저렴합니다. 특히 let
루프 바디 내부는 루프 바디 let
외부 보다 나쁘지 않습니다 (성능면에서) . 따라서 변수를 가능한 한 로컬에 바인딩하고 반복 변수를 루프 본문에 국한시키는 것이 좋습니다.
훨씬 적은 명령으로 컴파일되기 때문에 약간 빠릅니다. 다음에 나오는 병렬 분해를 고려하십시오 (오른쪽에 로컬 렛).
0 varref list 0 varref list
1 constant nil 1:1 dup
2 varbind it 2 goto-if-nil-else-pop 2
3 dup 5 dup
4 varbind temp 6 car
5 goto-if-nil-else-pop 2 7 stack-ref 1
8:1 varref temp 8 cdr
9 car 9 discardN-preserve-tos 2
10 varset it 11 goto 1
11 varref temp 14:2 return
12 cdr
13 dup
14 varset temp
15 goto-if-not-nil 1
18 constant nil
19:2 unbind 2
20 return
그러나 차이를 일으키는 원인은 전혀 없습니다.
varbind
어휘 바인딩 아래 컴파일 된 코드 가 없습니다 . 그것이 요점과 목적입니다.