Fast-Load 직렬화

Fast-Load 직렬화 (s-exp->fasl, fasl->s-exp)

Racket 값을 바이트 문자열로 빠르게 직렬화하고 다시 복원하는 fasl 기법을 알아봐요. s-exp->faslfasl->s-exp 한 쌍을 통해 값 그래프를 저장하고 불러올 수 있어요.

출처: Racket Reference

본문

13.10 Fast-Load 직렬화

(require racket/fasl)
package: base

이 절에서 문서화한 바인딩은 racket/baseracket이 아니라 racket/fasl 라이브러리가 제공합니다.

(s-exp->fasl v [out #:keep-mutable? keep-mutable?
                #:handle-fail handle-fail
                #:external-lift? external-lift?
                #:skip-prefix? skip-prefix?])
  → (or/c void bytes?)
v               : any/c
out             : (or/c output-port? #f) = #f
keep-mutable?   : any/c = #f
handle-fail     : (or/c #f (any/c . -> . any/c)) = #f
external-lift?  : (or/c #f (any/c . -> . any/c)) = #f
skip-prefix?    : any/c = #f
(fasl->s-exp in [#:datum-intern? datum-intern?
                #:external-lifts external-lifts
                #:skip-prefix? skip-prefix?]) → any/c
in             : (or/c input-port? bytes?)
datum-intern?  : any/c = #t
external-lifts : vector? = '#()
skip-prefix?   : any/c = #f

s-exp->fasl 함수는 v를 바이트 문자열로 직렬화하는데, out이 출력 포트면 그 포트로 직접 출력하고, 그렇지 않으면 바이트 문자열을 반환해요. fasl->s-exp 함수는 s-exp->fasl로 인코딩된 바이트 문자열(직접 제공되거나 입력 포트로 제공)에서 값을 디코딩합니다.

v 인자는 리터럴로 quote될 수 있는 값이어야 해요. 즉, (compile \',v)가 동작하고 write후에read가능한(syntax object가 없는) 값이거나, 그런 값에 [correlated objects](linklets)가 섞인 것이어야 합니다. 다만s-exp->fasl`이 만드는 바이트 문자열은 컴파일된 코드와 같은 형식을 사용하지는 않아요.

v 안의 어떤 값이 quote 리터럴로 유효하지 않고 handle-fail#f가 아니면, 중첩된 값에 대해 handle-fail이 호출되고, 그 값 자리에 handle-fail의 결과가 기록됩니다. handle-fail 프로시저는 대체 값을 반환하는 대신 예외를 일으킬 수도 있어요. handle-fail#f라면, 유효하지 않은 값이 만나면 exn:fail:contract 예외가 일어납니다.

external-lift?#f가 아니면, s-exp->faslv에서 만나는 각 값 v-sub를 받습니다. v-sub에 대해 external-lift?의 결과가 참이면 v-sub는 결과에 인코딩되지 않고 대신 외부로 들어 올려진(externally lifted) 것으로 취급돼요. 역직렬화하는 fasl->s-exp은 외부로 들어 올려진 각 값에 하나씩, 직렬화 시 external-lift?에 전달된 것과 같은 순서로 값을 가진 external-lifts 벡터를 받습니다.

(compile \',v)처럼 s-exp->fasl은 그래프 구조를 보존하지 않고, 순환을 지원하지 않으며, non-[prefab](structures) 구조체를 처리하지 않아요. 그래프 구조를 보존하고 순환 데이터를 처리하며 직렬화 가능한 구조체를 인코딩하려면 s-exp->fasl을 [serialize](serialization)와 엮으세요. s-exp->faslfasl->s-exp함수는 바이트코드 저장/로딩과 같은 방식으로 각각current-write-relative-directorycurrent-load-relative-directory`를(부족하면 current-directory로 대체) 참고해 경로를 상대 형식으로 저장하며, 제약된 srcloc 값(〔Printing Compiled Code〕 참고)도 유사하게 허용하고 변환해요.

s-exp->faslkeep-mutable?가 참으로 주어지지 않으면, fasl->s-exp이 결과를 디코딩할 때 v의 가변 값이 불변 값으로 교체됩니다. datum-intern?#f로 주어지지 않으면, fasl->s-exp이 만든 어떤 불변 값도 datum-intern-literal로 필터링됩니다. 이러한 기본값 덕분에 s-exp->faslfasl->s-exp의 합성은 writeread의 합성처럼 동작해요.

skip-prefix?#f가 아니면, 스트림을 직렬화로 식별하는 프리픽스가 s-exp->fasl에 의해 쓰이지 않고 fasl->s-exp에 의해 읽히지도 않습니다. 프리픽스를 생략하면 약간의 공간을 아낄 수 있는데, 작은 값을 직렬화할 때 유용하지만, fasl->s-exp에서 종종 유용한 새니티 체크를 포기하게 됩니다.

s-exp->fasl이 만드는 바이트 문자열 인코딩은 Racket 버전과 무관합니다. 단, 미래의 Racket 버전이 현재 인식되지 않는 확장을 도입하는 경우는 예외예요. 특히 s-exp->fasl의 결과는 (skip-prefix? 인자가 일관되기만 하면) 어떤 미래 버전의 fasl->s-exp에도 유효한 입력이 됩니다.

예시:

(define fasl (s-exp->fasl (list #("speed") 'racer #\!)))
fasl
; #"racket/fasl:\0\24\34\3 \1\23\5speed\16\5racer\r!"
(fasl->s-exp fasl)
; '(#("speed") racer #\!)

버전 6.90.0.21의 package base에서 변경됨: s-exp->fasl 형식을 버전 독립적으로 만들고 #:keep-mutable?#:datum-intern? 인자를 추가.

버전 7.3.0.7에서 변경됨: correlated objects 지원 추가.

버전 7.5.0.3에서 변경됨: #:handle-fail 인자 추가.

버전 7.5.0.9에서 변경됨: #:external-lift?#:external-lifts 인자 추가.

버전 8.9.0.4에서 변경됨: fxvectorsflvectors 지원 추가.

더 알아보기