본문으로 바로가기

Tcl의 바이트코드 (ByteCode)

category 카테고리 없음 2024. 7. 3. 12:54

Tcl은 버전 8.0부터 스크립트를 실행할 때 바이트코드를 채택하고 있습니다. 하지만 바이트코드로의 변환은 스크립트 실행직전에 내부에서 변환되기 때문에, 프로그래머나 유저도 이를 의식하지 못합니다.

Tcl스크립트 → 바이트코드 → 실행

바이트코드란, Tcl 인터프리터가 내부에서 실행시에 사용하는, 플랫폼에 의존하지 않는 중간언어라고 할 수 있습니다. 초기의 Tcl 인터프리터는 스크립트의 문자열을 [라인 by 라인]으로 해석하고 실행하기 때문에, 실행 속도의 지연이 문제가 되었었습니다. 바이트코드로의 변환을 채용한 것으로, 변환에 필요한 오버헤드를 고려해도, 종전 Tcl 인터프리터에 비교하여 상당한 실행 속도의 향상이 있었습니다.

바이트코드의 장점

Tcl/Tk 8.0이 공개될 당시, Tcl/Tk의 개발을 담당하던 Scriptics Corp. 로부터 TclPro 개발툴이 발매되었습니다. 이 툴에는 디버거나 바이트코드 컴파일등을 포함하고 있는데, 그후 TclPro는 오픈소스화되고, 현재는 Active State Corp로부터 개발된 TclPro를 발전시킨 TclDevkit이 판매되고 있습니다. 오픈소스가 된 TclPro 중, 바이트코드에 관한 툴은 procomp, tbcload가 있습니다. procomp는 Tcl 스크립트를 바이트코드로 변환하는 기능을 담당하는 툴이며, tbcload는 바이트코드로 변환된 파일을 읽고 실행하는 툴입니다. 그렇다면 바이트코드를 파일로 취급함으로써 얻는 장점은 무엇일까요? 스크립트를 실행 시에 Tcl 인터프리터 내부에서 바이트코드로 변환하기 때문에, 실행속도에 대한 차이는 거의 없습니다. 그러나 바이트코드를 파일로서 실행하는 경우의 장점은 다음과 같다고 생각됩니다.

  • 애플리케이션의 스크립트를 은폐할 수 있다.
  • 애플리케이션의 호환성이 향상된다.

처음의 장점중 ‘은폐’는 오픈소스의 세계에서는 의미가 없는 것이겠지만, 실용적으로 중요한 점입니다. 애플리케이션이 바이너리인 경우, 어셈블리를 컴퓨터만큼 능숙하게 하는 유저는 수정을 할 수도 있을지 모르겠지만, 보통의 경우는 손쉽게 수정을 가할 수 없습니다. 하지만 스크립트의 경우는 프로그램의 내용이 훤히 들여다 보이기 때문에, 확실히 프로텍트가 되어있지 많은한, 유저는 프로그램을 개선하고 수정하는 것을 쉽게 할 수 있습니다. 상업적으로 쓰이는 애플리케이션, 또는 공업적 생산라인 가동으로 쓰이는 애플리케이션의 경우는 사용자가 쉽게 고칠 수가 있다면 치명적인 문제를 일으킬 수 있습니다. 그 때문에, 프로그램의 내용이 훤히 들여다 보이는 것을 막을 필요가 있습니다. Tcl의 바이트코드는 암호화가 되어 있지 않기 때문에, 원칙적으로는 해독이 가능하지만, 그래도 스크립트의 내용이 훤히 들여다 보이는 것보단 훨씬 안전할 것입니다. 두 번째 경우 ‘호환성’의 향상은, 다른 부호화(encoding)를 갖는 우리나라의 경우는 중요한 포인트입니다. 바이트코드는 부호화에 의존하지 않고, 단일의 인코딩(encoding) UTF-8로 기술되고 있습니다.

UTF-8 이란

UTF-8이란 UCS Transformation Format의 약자이며, UCS는 Universal Character Set의 약자입니다. 간단히 말하면 유니코드(전 세계의 문자들을 표시하기 위한 표준 코드입니다)로 인코딩하는 방식중의 하나입니다. 예를 들면, ‘가’라는 글자는 UTF-8로 인코딩 되면 16진수 EAB080으로 바뀝니다. 예를 들어, 스크립트가 ksc5601로 기술되어 있어도(일본의 경우 shift-JIS), Tcl에 해석된 시점에서 UTF-8로 인코딩으로 변화되어 내부처리됩니다.

컴파일러(procomp)와 바이트코드 로더(tbcload)

다음의 예제를 바이트코드로 컴파일하여 실행을 해보도록 하겠습니다.

# -----------------------------------------------------------------------------
#  캔버스의 작성
# -----------------------------------------------------------------------------
proc 캔버스의작성 {} {
    # 메뉴바 작성
    set mbar .메뉴바
    . configure -menu $mbar
   # 메뉴
    menu $mbar
    $mbar add cascade \
            -label     "파일(F)" \
            -underline 5 \
            -menu      $mbar.file
    set menu $mbar.file
    menu $menu -tearoff no
    $menu add command \
            -label     "종료(X)" \
            -underline 3 \
            -command   {exit}
   # 캔버스
    set m1 .캔버스
    canvas $m1 \
            -width      300 \
            -height     300 \
            -background white
   # 계산결과
    set m2 .결과출력
    set m3 .스크롤
    listbox $m2 \
            -height 5 \
            -width 30 \
            -selectmode       single \
            -foreground       darkblue \
            -selectbackground darkgreen \
            -selectforeground yellow \
            -yscrollcommand   "$m3 set"
    scrollbar $m3 \
            -command "$m2 yview"
 
    grid $m1 -columnspan 2
    grid $m2 $m3 -sticky news
    grid rowconfigure . 0 -weight 1
    grid columnconfigure . 0 -weight 1
    update
    return [list $m1 $m2]
}
# -----------------------------------------------------------------------------
#  결과의 표시
# -----------------------------------------------------------------------------
proc 결과의표시 {표시 종류 치} {
    switch ${종류} {
        계산치 {
            set 횟수 [expr [${표시} index end] + 1]
            set 배경색 beige
            set 메세지 "π의 계산치(${횟수}) : ${치}"
        }
        시행횟수 {
            set 메세지 "시행회수 : ${치}"
            set 배경색 lavender
        }
        계측치 {
            set 시간 [expr [lindex ${치} 0] * 0.000001]
            set 메세지 "평균 계산 시간 : ${시간}초"
            set 배경색 lavender
        }
    }
    ${표시} insert end ${메세지}
    ${표시} itemconfigure end -background ${배경색}
    ${표시} see end
    return
}
# -----------------------------------------------------------------------------
#  점 출력
# -----------------------------------------------------------------------------
proc 점출력 {위젯 x y 색} {
    set d 1
    set x0 [expr $x * 300]
    set y0 [expr $y * 300]
    set x1 [expr $x0 - $d]
    set y1 [expr $y0 - $d]
    set x2 [expr $x0 + $d]
    set y2 [expr $y0 + $d]
    ${위젯} create oval $x1 $y1 $x2 $y2 \
            -fill    ${색} \
            -outline ${색} \
            -tags    출력
    update
    return
}
# -----------------------------------------------------------------------------
# π의계산(몬테카를로법)
# -----------------------------------------------------------------------------
proc π의계산 {시행횟수 출력화면 결과표시} {
    ${출력화면} delete 출력
    set 히트수 0
    for {set i 0} {$i < ${시행횟수}} {incr i} {
        set x [expr rand()]
        set y [expr rand()]
        set 우홀수 [expr $i % 2]
       # 반경 1 이내에 들어가는지 검사
        if {[expr hypot($x, $y)] <= 1} {
            incr 히트수
            if {${우홀수}} {
                set 색 red
            } else {
                set 색 blue
            }
        } else {
            if {${우홀수}} {
                set 색 orange
            } else {
                set 색 cyan
            }
        }
        점출력 ${출력화면} $x $y ${색}
    }
   # 반경 1 이내에 히트수를 시행한 횟수로 나누었던비는 π/4
    set π [expr double(${히트수})/double(${시행횟수})*4]
    결과의표시 ${결과표시} "계산치" ${π}
    return
}
# -----------------------------------------------------------------------------
#  메인
# -----------------------------------------------------------------------------
option add *Font {fixed 11}
wm title . "원주율의 계산(몬테 카를로법)"
set 시행횟수 10000
set 조반횟수 3
set 출력정보 [캔버스의작성]
set 출력영역 [lindex ${출력정보} 0]
set 결과표시 [lindex ${출력정보} 1]
#
set 계측결과 [time {π의계산 ${시행횟수} ${출력영역} ${결과표시}} ${조반횟수}]
결과의표시 ${결과표시} "시행횟수" ${시행횟수}
결과의표시 ${결과표시} "계측치"   ${계측결과}
# ---
# circle.tcl

circle.tcl을 아래와 같이 바이트 컴파일을 합니다.

컴파일후 circle.tbc 파일이 생성됩니다. 출력된 circle.tbc 파일을 적당한 텍스트 에디터로 읽어보면, 아래와 같이 보입니다.

출력된 *.tbc 파일을 한꺼번에 실행할 수 있도록, 모든 파일에 바이트코드를 실행할 수 있는 패키지 tbcload 커맨드가 삽입되고, 바이트코드로 변환된 원본 스크립트 부분이 bceval에 의해 실행되게 되어 있어서, 바이트코드를 읽을 수 있는 확장 패키지가 필요해집니다. 하지만 보통 유저는 직접 사용하지 않는 패키지입니다. 출력된 circcle.tbc 파일을 실행하면,

다음과 같은 원주율의 계산이 시작됩니다.

이 예제는, 의도적으로 변수명과 프로시져의 이름을 한글로 사용한 스크립트입니다. 바이트코드로 변환이 되어도 동작하는 데는 이상 없으며, 실행시간의 결과도 동일합니다.