ㅈㅅㄹ

이맥스에서도 ELISP 코드를 byte-compile해서 코드 성능을 향상 시킬 수 있다. Performance of Byte-Compiled Code를 읽어 보면 엄청나다고 광을 팔고 있긴 하지만, 사실 실 사용에서 그 정도까지 성능 향상이 되긴 어렵지 않을까 싶긴 한데 그래도 해 두면 일정 수준은 성능 향상을 기대 할 수 있긴 하다. 다만 설정 파일을 한번 만들어 놓고 나서 몇 주 동안 수정하지 않는 사람이라면 몰라도, 설정 파일을 모듈 별로 나눠 놓고 조금씩이나마 계속 하루에 한 번 이상은 이것 저것 건드려 대는 나 같은 사람이 설정파일 몇 줄 고치고 나서 byte-compile을 매번 일일히 하기에는 엄청나게 귀차니즘이 동반되기 때문에 사실상 손수 byte-compile을 한다는 건 불가능에 가깝지 않나 생각을 한다.



일단 여러 설정 파일들을 자동으로 byte-compile 하기 위해 아래와 같은 함수를 만든다

(defconst my:byte-compile-path '( "~/.emacs.d" "~/.emacs.d/utils" ))
(defun my:byte-compile-updated ()
  "Compile updated init files."
  (interactive)
  (dolist (dir my:byte-compile-path)
    (if (file-exists-p dir)
        (dolist (file (directory-files dir))
          (when (string-match "\\.el\\'" file)
            (let* ((src (concat dir "/" file))
                   (target (concat src "c")))
              (unless (and (file-exists-p target)
                           (file-newer-than-file-p target src))
                (byte-compile-file src))))))))

지정해 준 경로의 ELISP 파일을 찾아 byte-compile 된 것과 modified time 비교를 해서 ELISP 파일이 지난 번 byte-compile 때 이후로 변경되었다면 byte-compile-file을 호출해 준다. .emacs.d아래에는 내가 작성한 설정 파일 외에도 패키지 매니저를 통해 설치한 파일들도 많으므로 (그리고 그건 보통 설치시에 byte compile 해 놓고 건드리지 않으므로) 그냥 recursive하게 .emacs.d 아래의 ELISP 파일을 byte-compile 하는 것 보단 일일히 지정하는 편을 택했지만, 굳이 recursive 하게 하려면 아래와 같이 변형해 주면 된다.

(defun my:byte-compile-updated-recursively (&optional dir-input)
  "Compile updated init files."
  (interactive (list (read-directory-name "Directory: " "~/.emacs.d")))
  (let ((dir (or dir-input "~/.emacs.d")))
    (dolist (file (and (file-directory-p dir)
                       (directory-files dir)))
      (if (and (file-directory-p (concat dir "/" file))
               (not (member file '("." ".."))))
          (my:byte-compile-updated-recursively (concat dir "/" file))
        (when (string-match "\\.el\\'" file)
          (let* ((src (concat dir "/" file))
                 (target (concat src "c")))
            (unless (and (file-exists-p target)
                         (file-newer-than-file-p target src))
              (byte-compile-file src))))))))

...만, 애초에 recursive하게 할 거면 이 짓을 할 필요 가 없이 byte-recompile-directory 함수를 쓰면 된다.


이제 이 함수를 언제 호출할 것인가가 관건인데, 내가 생각하는 가장 적절한 타이밍은 역시 이맥스를 종료할 때 인 것 같다. kill-emacs-hook에 hook을 추가 해 주자.

(add-hook 'kill-emacs-hook 'my:byte-compile-updated)

이제 종료할 때 지정된 디렉토리에서 업데이트 된 ELISP 파일이 있으면 자동으로 업데이트 해 줄 것이다. 다만 이런 설정을 했을 때 문제점은 이맥스를 시작 하다가 my:byte-compile-updated이 evaluation 되기 전에 어떤 ELISP코드에서 에러가 날 경우, 에러를 수정해서 저장하고 종료하더라도 종료시에 수정된 사항을 반영하여 byte-compile을 다시하지 않을 것이므로 다음번 이맥스 시작시에 실제로 수정 사항이 반영되지 않는다는 점이다. 이 이유는 이맥스가 byte-compile된 .elc 파일이 존재할 경우 modification time에 상관 없이 우선적으로 byte-compile 된 파일을 읽어들이기 때문으로, 이 때는 수동으로 .elc을 지워줘야 하는 번거로움이 있긴 했다.


그래서 예전에는 위의 byte-compile 함수나 hook 설정을 설정 파일 초반에 넣어서 코드가 무조건 evaluation 되게 만들어 종료시에 byte-compile을 꼭 하도록 하는 방식으로 위와 같이 꼬이는 문제를 해결했지만, 다행히 GNU Emacs 24.4 이후로 load-prefer-newer라는 변수가 추가 되었는데 이 변수를 non-nil로 세팅해 주면, loadrequire 함수를 호출해서 ELISP 파일을 로딩할 때 .elc가 존재하더라도 .el 파일의 수정일이 더 나중이라면 이 쪽을 로딩하도록 해 준다. 물론 변수 설정은 실제 라이브러리 로딩 하는 부분 이전에 세팅해줘야 하겠지만 어쨌든 이걸 쓰면 위의 문제는 해결된다.