OpenSees Tcl 읽기: 20년치 공개 스크립트와, 단어 대 단어 이식이 답을 바꾸는 두 곳
OpenSees 저장소에는 Tcl 파일 259개에 파이썬 35개가 있고 EXAMPLES 아래는 156 대 23이다. Tcl 읽기를 거부하는 것은 이 프레임워크가 가진 가장 좋은 문서를 버리는 일이다. 같은 240스텝 비선형 푸시오버를 Tcl과 파이썬으로, 서로 독립 컴파일된 두 빌드에서 돌린 결과가 변위
OpenSees 소스 저장소에는 .tcl 파일이 259개, .py 파일이 35개 있습니다. EXAMPLES/ 아래에서는 156 대 23, Workshops/ 아래에서는 82 대 2입니다. 20년치 예제와 학위논문 부록과 워크숍 자료가 대부분의 신규 사용자가 한 번도 만져본 적 없는 언어로 쓰여 있고, 흔한 반응 — 파이썬 버전을 찾아보고, 없으면 포기 — 은 OpenSees가 가진 가장 좋은 문서를 통째로 버리는 것입니다.
좋은 소식은 측정 가능합니다. 같은 철근콘크리트 기둥을 비선형 영역까지 240스텝 변위제어로 밀었을 때, Tcl 스크립트로 한 번 파이썬 스크립트로 한 번, 서로 독립적으로 컴파일된 두 빌드에서 돌린 결과의 지붕 변위가 2.2 × 10⁻¹⁹ m까지 같고 밑면전단력이 상대오차 3.7 × 10⁻¹⁰으로 일치합니다. 아래에는 하나의 객체 모델이 있고, 언어는 그 위에 씌운 얇은 껍질입니다. 이 글은 그 껍질을 읽는 법입니다. 기계적으로 옮겨지는 것, 단어 대 단어 이식이 답을 조용히 바꿔놓는 두 곳, 그리고 오류 메시지 없이 맞는 모델을 틀린 모델로 만드는 Tcl 언어 거동 네 가지.
이 글은 10부입니다. 1–9부에서 객체 모델, 탄성 골조, 파이버 단면, 동적해석, 소스 읽기, 철근콘크리트, 지진기록, 3차원 건물, 수렴 실패를 다뤘습니다.
1. 실제로 Tcl로 쓰여 있는 것
디렉터리.tcl.py
전체25935
EXAMPLES/15623
Workshops/822
DEVELOPER/110
이건 공식 저장소만 센 것입니다. PEER 예제, 공개된 검증 스위트, 그리고 대부분의 논문 부록도 Tcl입니다. Tcl을 읽는 건 향수가 아니라, 다른 형태로는 존재하지 않는 자료에 접근하는 방법입니다.
2. 네 가지 규칙이 거의 전부를 옮긴다
명령은 함수다. 첫 단어가 함수 이름이고 나머지가 순서대로 인자입니다.
node 1 0.0 0.0 -> ops.node(1, 0.0, 0.0)
맨단어는 문자열이다. Tcl에서 Linear에는 따옴표가 없지만 파이썬에는 필요합니다.
geomTransf Linear 1 -> ops.geomTransf('Linear', 1) uniaxialMaterial Steel01 3 400000.0 200000000.0 0.01 -> ops.uniaxialMaterial('Steel01', 3, 400000.0, 200000000.0, 0.01)
$var는 그냥 var다. 달러 기호는 Tcl의 치환 연산자일 뿐 그 이상이 아닙니다.
element Truss 1 1 2 $A 1 -> ops.element('Truss', 1, 1, 2, A, 1)
[expr {...}]는 그냥 산술이다. Tcl은 계산을 하려면 명령이 필요하고, 파이썬은 아닙니다.
patch rect 2 12 1 [expr -$D/2.0+$cov] [expr -$B/2.0+$cov] \ [expr $D/2.0-$cov] [expr $B/2.0-$cov] -> ops.patch('rect', 2, 12, 1, -D/2+cov, -B/2+cov, D/2-cov, B/2-cov)
문법 두 개만 더 알면 공개된 어떤 스크립트든 읽을 수 있습니다. 줄 맨 끝의 역슬래시는 줄을 잇는데, 정말로 맨 끝 문자여야 합니다. 뒤에 공백 하나만 붙어도 다음 줄이 독립된 명령이 되어버립니다. 그리고 #은 명령이 시작될 수 있는 자리에서만 주석이므로, 줄 끝 주석은 앞에 세미콜론이 필요합니다. analyze 1 ;# 한 스텝.
3. 같은 모델, 두 언어, 두 빌드
객체 모델이 같다는 주장은 경로 의존적인 대상에서 시험해야 의미가 있습니다. 구성식 적분에 차이가 있으면 누적되기 때문입니다. 모델은 6부의 기둥입니다. 400 × 600에 구속코어·피복·철근 8개, forceBeamColumn Lobatto 5점, PDelta 변환, 중력 0.10 f'c Ag, 그다음 0.5 mm씩 240스텝 변위제어.
양결과
최대 밑면전단력양쪽 모두 210.076768 kN
최대 |Δ 지붕변위|2.17 × 10⁻¹⁹ m
최대 |Δ 밑면전단력|7.88 × 10⁻⁸ kN — 상대 3.75 × 10⁻¹⁰
이 비교에서 Tcl 쪽은 파이썬 쪽과 다르게 컴파일된 OpenSees 빌드에서 돌았습니다. 그래서 이 일치는 약해지는 게 아니라 강해집니다. 컴파일러 둘, 언어 둘, 240스텝 소성 경로에서 유효숫자 열 자리까지 같은 답 하나.
두 번째 시험은 제 스크립트가 아니라 남의 스크립트를 씁니다. EXAMPLES/verification/PlanarTruss.tcl은 공식 OpenSees 검증 스위트의 일부로, 3부재 평면 트러스를 만들어 Popov 교과서의 닫힌해와 허용오차 10⁻⁶으로 대조합니다. 이것을 줄 단위로 파이썬에 옮기면 같은 판정을 통과합니다.
PASSED - python port of PlanarTruss.tcl element 1 OpenSees 65.2447 Popov 65.2447 err 2.842e-14 element 2 OpenSees 86.9929 Popov 86.9929 err 1.421e-14 element 3 OpenSees 65.2447 Popov 65.2447 err 2.842e-14 linear disp OpenSees -0.57995269 Popov -0.57995269 err 1.110e-16 elastic limit OpenSees -4.00000000 Popov -4.00000000 err 8.882e-16 collapse limit OpenSees -5.33333333 Popov -5.33333333 err 2.665e-15
그 디렉터리의 모든 검증 스크립트를 같은 방식으로 가져올 수 있고, 그게 실무적인 요점입니다. 공식 검증 스위트가 내 설치 환경을 검사하는 파이썬 테스트 스위트가 됩니다.
4. 첫 번째 예외: 인라인 적분
Tcl은 요소 명령 안에서 적분 방식을 선언할 수 있습니다.
element forceBeamColumn 1 1 2 1 Lobatto 1 5
단어 대 단어로 옮기면 ops.element('forceBeamColumn', 1, 1, 2, 1, 'Lobatto', 1, 5)가 되고, OpenSees는 이렇게 답합니다.
WARNING invalid int inputs opensees.OpenSeesError: See stderr output
메시지는 어떤 인자인지도, 어떤 요소인지도, 어떤 단어를 못 읽었는지도 말하지 않습니다. 파이썬 API에는 인라인 형식이 없고, 적분은 자기 태그를 가진 별도 객체입니다.
ops.beamIntegration('Lobatto', 1, 1, 5) # 태그 1, 단면 1, 5점 ops.element('forceBeamColumn', 1, 1, 2, 1, 1) # ..., 변환 1, 적분 1
공개 스크립트를 옮길 때 가장 흔히 막히는 지점이고, 친절한 종류의 실패입니다. 멈추니까요. 다음 것은 멈추지 않습니다.
5. 두 번째 예외: 중괄호는 스코프가 아니다
공개된 파이버 단면은 전부 이렇게 생겼고, 이 모양은 거짓말입니다.
section Fiber 1 { patch rect 2 12 1 ... layer straight 3 4 $Ab ... }
중괄호는 단면을 열고 닫는 블록처럼 보입니다. 아닙니다. section이 단면을 열고, 블록이 실행되고, 닫는 중괄호를 지난 뒤에도 단면은 여전히 열려 있습니다. 그 뒤에 쓰인 patch는 — 정리하던 이식자가, 병합이, 복사-붙여넣기가 남긴 것이든 — 안에 있었던 것과 똑같이 단면 1에 더해집니다.
해석축방향 강성 EA/L의도 대비
의도한 값, 0.30 × 0.30, E = 3.0 × 10⁷ kPa2,700,000 kN/m1.000000
Tcl, patch 하나 더5,400,000 kN/m2.000000
Python, patch 하나 더5,400,000 kN/m2.000000
저는 Tcl이 이걸 거부하고 파이썬이 받아들일 거라고 예상했습니다. 둘 다 받아들이고, 둘 다 의도보다 정확히 두 배 뻣뻣한 기둥을 내놓으며, 어떤 상세도에서도 경고가 없습니다. 교훈은 "파이썬이 더 위험하다"가 아닙니다. 여러분이 지금까지 읽은 모든 파이버 단면의 들여쓰기는 장식이고, 단면을 닫는 유일한 것은 다른 객체를 여는 일이라는 겁니다.
실무적인 방어는 한 줄입니다. 단면을 만든 뒤 손으로 계산한 EA와 축방향 강성을 대조하십시오. 5부의 검증 테스트 스위트가 정확히 이 부류의 오류를 위해 있고, 이건 판정 하나면 잡힙니다.
6. Tcl 산술: 소수점 하나
Tcl의 expr는 두 피연산자가 모두 정수면 정수 연산을 하고, 경고하지 않습니다.
expr 1/2 -> 0 expr 1/2*0.01 -> 0.0 expr 200000/3 -> 66666 expr 200000/3.0 -> 66666.66666666667 expr 60/3000 -> 0 expr 60.0/3000.0 -> 0.02
마지막 쌍이 문제입니다. fy/E가 바로 항복변형률을 쓰는 방식이기 때문입니다. 정수 버전을 ElasticPP에 넣으면 부재가 변형률 0에서 항복하고, 그건 영원히 힘을 하나도 못 받는다는 뜻입니다.
두 해석 모두 analyze()가 매번 0을 반환했습니다. 소수점이 빠진 모델은 20스텝 전부에서 축력 0.0000 kN인 부재를 내놓았고(정상 모델은 600.0000 kN), 깔끔하게 수렴했고, 아무것도 알리지 않았습니다. 잘 쓴 공개 스크립트마다 소수점이 여기저기 흩뿌려져 있는 이유가 이것입니다. set E 3000., set alpha 30.0, /180. 스타일이 아니라 저자의 방어입니다.
읽을 때 알아둘 expr 거동 두 개가 더 있습니다. 중괄호 없는 expr는 변수의 텍스트를 치환한 뒤 파싱하므로, 식을 담은 변수는 평가되는 게 아니라 끼워넣어집니다.
set b "1+2" expr $b*3 -> 7 # 1 + 2*3 으로 파싱된다 expr {$b*3} -> error: can't use non-numeric string as operand of "*"
그리고 중괄호를 쓰면 식이 매번이 아니라 한 번만 컴파일되므로 측정 가능하게 빠릅니다. 30만 회 기준으로 중괄호 없이 344 ms, 중괄호로 121 ms, 2.85배입니다. 모든 expr에 중괄호를 쓰십시오. 안 쓸 이유는 위의 이중치환 트릭뿐이고, 그건 원할 만한 게 아닙니다.
7. 이름 하나가 충돌하고, 하필 가장 많이 쓰는 것이다
기본 Tcl 8.6 인터프리터가 정의하는 101개 명령 중 OpenSees 명령과 이름이 겹치는 것은 정확히 하나, load입니다. Tcl의 load는 공유 라이브러리를 적재합니다. OpenSees의 load는 절점하중을 겁니다. Tcl 스크립트에서 OpenSees 쪽 의미가 확실한 자리는 pattern 블록 안입니다. 그 밖에서 흘러나온 load가 바로 충돌 대기 상태이고, 그때 나오는 오류는 구조해석이 아니라 패키지와 공유 라이브러리를 이야기합니다. 파이썬에서는 ops.load가 모듈의 이름이므로 충돌 자체가 없습니다.
나머지는 깨끗합니다. 이건 긍정형으로 말할 값어치가 있습니다. OpenSees 명령 집합과 Tcl 언어는 거의 겹치지 않으므로, Tcl 스크립트를 읽는 일은 대부분 OpenSees를 읽는 일이고, 그것을 이어주는 건 set, expr, for, foreach, proc, if, puts, open, close, lindex, llength, lappend뿐입니다. 열두 단어.
8. 출력 정밀도: 두 언어 모두 기본값이 6자리
이건 Tcl 문제가 전혀 아니지만, Tcl 스크립트의 출력을 파이썬 이식본과 대조하다 일곱째 자리에서 어긋나는 걸 발견할 때 만나게 됩니다.
recorder 기본값 -> 1 -0.0208333 recorder -precision 16 -> 1 -0.02083333333333333 nodeDisp 2 2 -> -0.020833333333333332
Tcl과 파이썬에서 동일합니다. recorder는 더 요구하지 않으면 유효숫자 6자리를 씁니다. 이와 별개로 Tcl의 tcl_precision 변수는 puts가 쓰는 자릿수만 제어하고 recorder에는 아무 영향이 없습니다. Tcl 8.6의 기본값은 0(왕복 가능한 최단 표기)이지만, 이 값을 다른 것으로 설정한 스크립트는 자기가 출력하는 모든 숫자를 조용히 잘라냅니다.
tcl_precisionputs [expr {1.0/3.0}]
0 (8.6 기본값)0.3333333333333333
60.333333
120.333333333333
170.33333333333333331
6자리로 충분한지는 그 파일로 다음에 무엇을 하느냐에 전적으로 달려 있습니다.
240스텝 푸시오버를 유효숫자 6자리로 반올림하면 밑면전단력은 상대오차 최대 3.7 × 10⁻⁶만큼 바뀝니다. 보이지 않는 수준입니다. 같은 반올림 데이터로 구한 중앙차분 접선강성은 중앙값 0.11%, 최악 163%만큼 틀립니다. 참 접선이 0.38 kN/m인 항복 평탄부에서 반올림 데이터는 1.00을 내놓습니다. 미분해서 쓰는 것 — 접선강성, 힘–변위 이력에서 구한 이력에너지, 변위에서 구한 속도 — 은 그것을 만든 recorder에 -precision 16이 필요합니다. 읽기만 하는 것은 아무것도 필요 없습니다.
9. 언어가 치르는 값, 그리고 치르지 않는 값
동일한 200만 회 산술 루프가 Tcl에서 835 ms, 파이썬에서 203 ms 걸립니다. 4.12배이고, 둘 다 마지막 비트까지 같은 합 1999999199999.9329를 쌓습니다. 실제 차이이고, 거의 항상 무의미합니다. OpenSees 스크립트에서 중요한 루프는 for {set i 1} {$i<=N} {incr i} {analyze 1 $dt}이고, analyze 안의 모든 것은 C++에서 돌기 때문입니다. 위의 240스텝 푸시오버에서 스크립트 언어가 차지한 실행시간은 1퍼센트의 몇 분의 일이었습니다.
새 작업을 파이썬으로 쓸 이유는 실재하지만 속도는 아닙니다. 후처리용 NumPy, 5부의 검증 스위트를 위한 pytest, 그리고 디버거입니다. Tcl을 읽을 이유는 20년치 모델이 거기 있다는 것입니다.
10. 이식 점검표
모든 [expr {...}]를 파이썬 산술로 바꾸면서, 각각을 정수 나눗셈인지 확인한다.
모든 맨단어에 따옴표를 씌운다. Linear, Steel01, -file, disp.
인라인 Lobatto/Legendre/HingeRadau를 자기 태그를 가진 beamIntegration 호출로 바꾼다.
모든 중괄호 블록을 장식으로 취급한다. patch·layer·fiber·load가 엉뚱하게 열린 객체에 붙지 않았는지 확인한다.
미분해서 쓸 모든 recorder에 -precision 16을 준다.
원본 스크립트가 출력하는 스칼라 하나 — 주기, 선단변위, 밑면전단력 — 를 먼저 대조한 다음에야 그 뒤를 믿는다.
스크립트에 검증 판정이 딸려 있으면 그것도 옮긴다. 이식이 됐는지 알려주는 건 그 부분뿐이다.
11. 같은 스크립트를 다섯 단계 깊이로
입문자는 node 1 0.0 0.0과 ops.node(1, 0.0, 0.0)이 같은 호출임을 알아봅니다. 주니어는 $, [expr ...], set, for를 막힘 없이 읽어 공개 예제를 끝까지 따라갑니다. 실무 해석자는 스크립트를 옮기고 원본이 출력하는 스칼라로 이식을 검증합니다. 시니어는 옮겨지지 않는 구문 — 인라인 적분, 중괄호 블록, 패턴 밖의 load — 을 알고 그것부터 확인합니다. 전문가는 OpenSees 소스의 Tcl 명령 등록부를 읽어 그 명령이 실제로 무엇을 받는지 알아냅니다. 오래된 명령일수록 문서가 코드보다 얇기 때문입니다.
다음 글은 1차원 요소를 떠납니다. 쉘·솔리드·지반 — ShellMITC4, 사각형 요소, 육면체 요소, 그리고 재료가 1축 구성식이 아니라 3차원 구성식일 때 무엇이 달라지는지.
전체 스크립트
공식 OpenSees 검증 스크립트를 줄 단위로 옮기고, 같은 교과서 답과 같은 허용오차로 대조합니다.
# Part 10 - the official OpenSees verification script, ported line for line import math import openseespy.opensees as ops A, E, L, alpha, P = 10.0, 3000., 200.0, 30.0, 200.0 sigmaYP = 60.0 pi = 2.0 * math.asin(1.0) alphaRad = alpha * pi / 180. cosA, sinA = math.cos(alphaRad), math.sin(alphaRad) F1 = P / (2 * cosA**3 + 1) F2 = F1 * cosA * cosA disp = -F1 * L / (A * E) ops.wipe(); ops.model('Basic', '-ndm', 2, '-ndf', 2) dX = L * math.tan(alphaRad) ops.node(1, 0.0, 0.0); ops.node(2, dX, 0.0) ops.node(3, 2.0 * dX, 0.0); ops.node(4, dX, -L) ops.fix(1, 1, 1); ops.fix(2, 1, 1); ops.fix(3, 1, 1) ops.uniaxialMaterial('Elastic', 1, E) for t, n in ((1, 1), (2, 2), (3, 3)): ops.element('Truss', t, n, 4, A, 1) ops.timeSeries('Linear', 1); ops.pattern('Plain', 1, 1); ops.load(4, 0., -P) ops.numberer('Plain'); ops.constraints('Plain'); ops.algorithm('Linear') ops.system('ProfileSPD'); ops.integrator('LoadControl', 1.0) ops.analysis('Static'); ops.analyze(1) tol = 1.0e-6 forces, nl = [], [] ok = True for i, exact in ((1, F2), (2, F1), (3, F2)): f = ops.eleResponse(i, 'axialForce')[0] forces.append({"ele": i, "opensees": f, "popov": exact, "err": abs(f - exact)}) ok &= abs(f - exact) <= tol d = ops.nodeDisp(4, 2) ok &= abs(d - disp) <= tol PA = (sigmaYP * A) * (1.0 + 2 * cosA**3); dispA = PA / P * disp PB = (sigmaYP * A) * (1.0 + 2 * cosA); dispB = dispA / (cosA * cosA) ops.wipe(); ops.model('Basic', '-ndm', 2, '-ndf', 2) ops.node(1, 0.0, 0.0); ops.node(2, dX, 0.0) ops.node(3, 2.0 * dX, 0.0); ops.node(4, dX, -L) ops.fix(1, 1, 1); ops.fix(2, 1, 1); ops.fix(3, 1, 1) ops.uniaxialMaterial('ElasticPP', 1, E, sigmaYP / E) for t, n in ((1, 1), (2, 2), (3, 3)): ops.element('Truss', t, n, 4, A, 1) ops.timeSeries('Path', 1, '-dt', 1.0, '-values', [0.0, PA, PB, PB]) ops.pattern('Plain', 1, 1); ops.load(4, 0., -1.0) ops.numberer('Plain'); ops.constraints('Plain'); ops.algorithm('Linear') ops.system('ProfileSPD'); ops.integrator('LoadControl', 1.0) ops.analysis('Static') ops.analyze(1); dA = ops.nodeDisp(4, 2) ops.analyze(1); dB = ops.nodeDisp(4, 2) for name, got, exact in (("elastic limit", dA, dispA), ("collapse limit", dB, dispB)): nl.append({"state": name, "opensees": got, "popov": exact, "err": abs(got - exact)}) ok &= abs(got - exact) <= tol print("PASSED" if ok else "FAILED", "- python port of PlanarTruss.tcl") for f in forces: print(f" element {f['ele']} OpenSees {f['opensees']:12.4f} Popov {f['popov']:12.4f} err {f['err']:.3e}") print(f" linear disp OpenSees {d:12.8f} Popov {disp:12.8f} err {abs(d-disp):.3e}") for n in nl: print(f" {n['state']:14s} OpenSees {n['opensees']:12.8f} Popov {n['popov']:12.8f} err {n['err']:.3e}") assert ok, 'the port does not reproduce the published verification'
실행하면 이렇게 나옵니다.
PASSED - python port of PlanarTruss.tcl element 1 OpenSees 65.2447 Popov 65.2447 err 2.842e-14 element 2 OpenSees 86.9929 Popov 86.9929 err 1.421e-14 element 3 OpenSees 65.2447 Popov 65.2447 err 2.842e-14 linear disp OpenSees -0.57995269 Popov -0.57995269 err 1.110e-16 elastic limit OpenSees -4.00000000 Popov -4.00000000 err 8.882e-16 collapse limit OpenSees -5.33333333 Popov -5.33333333 err 2.665e-15
OpenSees 시리즈 전체
각 편은 닫힌해·독립적인 풀이·OpenSees 소스 중 하나와 대조해 검증했다. 순서대로 읽도록 썼지만 각 편이 자기 전제를 스스로 밝힌다.
1부 — 도메인 모델과 해석 조립
2부 — 단면·기하변환·분포하중
3부 — 파이버 단면·모멘트–곡률·푸시오버
4부 — 질량·감쇠·시간적분
5부 — 라이브러리 읽기와 재현 가능한 워크플로
6부 — 철근콘크리트 파이버 단면과 구속
7부 — 실제 지진기록·층간변위·증분동적해석
8부 — 3차원: 강막·비틀림·leaning column
9부 — 수렴하지 않을 때: 실패·알고리즘·한계점
10부 — Tcl 읽기와 공개 스크립트 이식 — 지금 보는 글
11부 — 쉘·솔리드·지반: 락킹·메시·부지응답
12부 — 후처리: recorder·질의, 그리고 그림이 증명하는 것
13부 — 좌굴과 한계점: 분기점·스냅백·호장법
14부 — Windows: 설치·파일 형식·조용한 실패
15부 — 검증된 정답이 딸린 연습문제와 한영 용어집
16부 — 어떻게 동작하는가: 시행상태·확정상태·해석 루프
17부 — 커뮤니티가 말하는 것, 측정해보면
18부 — 절점 변위에서 파이버 응력까지
19부 — 면진과 감쇠: 속도 의존 요소와 베어링
참고 자료
OpenSees, EXAMPLES/verification — 공식 검증 스위트. 3절에서 쓴 PlanarTruss.tcl 포함.
OpenSees, EXAMPLES, Workshops — 1절에서 센 자료.
Popov, E. P., Engineering Mechanics of Solids, Prentice Hall, 1990 — 예제 2.14, 2.23. 검증 스크립트가 대조하는 닫힌해.
Tcl Developer Xchange, expr — 피연산자 타입과 중괄호가 중요한 이유.
Tcl Developer Xchange, tclvars — tcl_precision과 그 기본값.
OpenSeesPy, forceBeamColumn, beamIntegration — 4절의 분리.
OpenSeesPy, recorder — 8절의 -precision 옵션.
Pacific Earthquake Engineering Research Center, OpenSees Wiki — 파이썬 문서가 다루지 않는 명령의 Tcl 레퍼런스.
출처 확인 2026-08-31. 파이썬 결과는 openseespy 3.8.0으로 산출했습니다. Tcl 결과는 opensees 파이썬 패키지로 배포되는 유지보수 중인 OpenSees Tcl 인터프리터로 산출했으며, 이는 별도로 컴파일된 빌드입니다. 240스텝 소성 경로에서 둘이 3.7e-10까지 일치한다는 것은 가정이 아니라 교차검증입니다. 순수 Tcl 언어 거동은 기본 tclsh 8.6.14에서 측정했습니다. 시간 측정치는 한 대의 기계에서 나온 것으로, 절대값이 아니라 비율로 읽어야 합니다. 결과는 프레임워크의 거동을 보여주는 것으로, 프로젝트별 해석이나 설계기준 검토를 대체하지 않습니다.