일반적으로 어휘 바인딩과 동적 바인딩
다음 예제를 고려하십시오.
(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 캐싱에서 좋지 않은 반복 힙 메모리 액세스가 포함됩니다. 또한 동적 변수 바인딩 은의 끝에서 이전 변수 로 복원 해야하므로 바인딩이있는 각 블록에 대한 추가 조회 가 추가 됩니다.letnletn
만약 당신 바인드 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본체 바운드 변수 복원 할 필요가 루프의 각 반복 가산, 각 반복에 추가 변수 조회 . 따라서 전체 루프가 끝난 후 반복 변수가 한 번만 재설정되도록 루프 본문을 내보내는 것이 더 빠릅니다 . 그러나 반복 변수가 실제로 필요하기 전에 바인딩되어 있기 때문에 이것은 특히 우아하지 않습니다.
어휘 바인딩을 사용하면 lets가 저렴합니다. 특히 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어휘 바인딩 아래 컴파일 된 코드 가 없습니다 . 그것이 요점과 목적입니다.