Skip to content

Diff Engines

Won-Kyu Park edited this page Aug 28, 2026 · 2 revisions

Diff ์—”์ง„

SimpleRCS ์•ˆ์—๋Š” diff ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ์—ฌ๋Ÿฌ ๋ฒŒ ์žˆ๋‹ค. ์™œ ์ด๋ ‡๊ฒŒ ๋งŽ๊ณ , ์‹ค์ œ๋กœ ๋ญ๊ฐ€ ๋Œ์•„๊ฐ€๊ณ  ์žˆ๊ณ , ๋‚˜๋จธ์ง€๋Š” ์™œ ์•„์ง๋„ ๋ถ™์–ด์žˆ๋Š”์ง€๋ฅผ ์ •๋ฆฌํ•œ๋‹ค.

์–ด๋А ๊ฒŒ ์‹ค์ œ๋กœ ๋„๋Š”๊ฐ€

matchers.py๊ฐ€ import ์‹œ์ ์— ๋ฐฑ์—”๋“œ ํ•˜๋‚˜๋ฅผ ์ •ํ•˜๊ณ , commit()/checkout()์€ ๊ทธ๊ฑธ ์“ด๋‹ค. ์ˆœ์„œ๋Š” ์ด๋ ‡๋‹ค.

SIMPLE_RCS_MATCHER (์„ค์ •๋ผ ์žˆ์œผ๋ฉด) -> Cython Myers (dmp -> ses) -> StreamSequenceMatcher

์ฆ‰ C ํˆด์ฒด์ธ์ด ์žˆ์–ด Cython ํ™•์žฅ์ด ๋นŒ๋“œ๋ผ ์žˆ์œผ๋ฉด ๊ธฐ๋ณธ ๋ฐฑ์—”๋“œ๋Š” Cython Myers(dmp_cython)์ด๊ณ , StreamSequenceMatcher๋Š” ๊ทธ๊ฒŒ ์—†์„ ๋•Œ์˜ ํด๋ฐฑ์ด๋‹ค. ํ™•์ธ์€ ์ด๋ ‡๊ฒŒ ํ•œ๋‹ค.

>>> from simple_rcs.matchers import ACTIVE_BACKEND
>>> ACTIVE_BACKEND
'dmp_cython'

ํด๋ฐฑ์ด ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ Myers ์Œ๋‘ฅ์ด(myersdiff_*.py)๋กœ ๊ฐ€์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค โ€” ๊ทธ์ชฝ์€ ์ดˆ๊ธฐ ๊ตฌํ˜„ ๋ ˆํผ๋Ÿฐ์Šค์ด๊ณ  ์„œ๋กœ ๋‹ค๋ฅธ ์ž…๋ ฅ์—์„œ O(ND) ์ตœ์•…์„ ๋ฐŸ๋Š”๋‹ค. ์ด๋ฆ„์œผ๋กœ๋Š” ์„ ํƒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํด๋ฐฑ ๋Œ€์ƒ์€ ์•„๋‹ˆ๋‹ค.

SIMPLE_RCS_MATCHER์— ๋„ฃ๋Š” ์ด๋ฆ„์€ tools/bench_diff.py๊ฐ€ ์ถœ๋ ฅํ•˜๋Š” id์™€ ๊ฐ™์•„์„œ, ๋ฒค์น˜๋งˆํฌ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋Œ€๋กœ ์„ค์ •์œผ๋กœ ์˜ฎ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

SIMPLE_RCS_MATCHER=ses_cython uv run tools/srcs_commit.py FILE -m msg

๋ฐฑ์—”๋“œ๋ฅผ ๋ฐ”๊พธ๋ฉด ์ €์žฅ๋˜๋Š” ๋ธํƒ€ ๋ฐ”์ดํŠธ๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค. ๋˜‘๊ฐ™์ด ์œ ํšจํ•œ ์—ฌ๋Ÿฌ opcode ์‹œํ€€์Šค ์ค‘ ์–ด๋А ๊ฒƒ์„ ๊ณ ๋ฅด๋А๋ƒ๊ฐ€ ๋‹ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ธ”๋ก ํ•ด์‹œ๋Š” ์ €์žฅ ํ‘œํ˜„์ด ์•„๋‹ˆ๋ผ ๋…ผ๋ฆฌ์  ์ „์ฒด ๋‚ด์šฉ์„ ๋ฎ์œผ๋ฏ€๋กœ, ๋ฐฑ์—”๋“œ๋ฅผ ๊ฐˆ์•„๋„ ๊ธฐ์กด ์ด๋ ฅ์˜ ๊ฒ€์ฆ์€ ๊ณ„์† ํ†ต๊ณผํ•œ๋‹ค.

StreamSequenceMatcher๊ฐ€ ํ•˜๋Š” ์ผ

ํ•ด์‹œ ๊ธฐ๋ฐ˜ ๊ทธ๋ฆฌ๋”” ๋งค์นญ์ด๋‹ค. ๋Œ€๋žต ์ด๋Ÿฐ ์ˆœ์„œ๋กœ ๋™์ž‘ํ•œ๋‹ค.

  1. ์–‘์ชฝ ์‹œํ€€์Šค๋ฅผ ํ•ด์‹œ๋กœ ์ธ๋ฑ์‹ฑํ•ด์„œ ๋™์ผํ•œ ๋ผ์ธ๋“ค์„ ๋น ๋ฅด๊ฒŒ ์ฐพ๋Š”๋‹ค
  2. ๋งค์นญ๋˜๋Š” ๋ธ”๋ก๋“ค์„ ๊ทธ๋ฆฌ๋””ํ•˜๊ฒŒ ๊ณจ๋ผ ๋ถ™์ธ๋‹ค
  3. ๋งค์นญ ์•ˆ ๋˜๊ณ  ๋‚จ์€ "๊ต์ฒด(replace)" ๊ตฌ๊ฐ„๋งŒ ํ‘œ์ค€ difflib์œผ๋กœ ๋‹ค๋“ฌ๋Š”๋‹ค

์ตœ๋‹จ ํŽธ์ง‘ ๊ฑฐ๋ฆฌ๋ฅผ ์ˆ˜ํ•™์ ์œผ๋กœ ๋ณด์žฅํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค. ํ•˜์ง€๋งŒ O(N)์— ๊ฐ€๊น๊ฒŒ ๋น ๋ฅด๊ณ , ์‹ค๋ฌด์—์„œ ๋‚˜์˜ค๋Š” diff ๊ฒฐ๊ณผ๊ฐ€ ๋Œ€๋ถ€๋ถ„ ์ถฉ๋ถ„ํžˆ ์ž์—ฐ์Šค๋Ÿฝ๋‹ค. ์ปค๋ฐ‹ ๊ฒฝ๋กœ์ฒ˜๋Ÿผ "์ •ํ™•ํ•œ ์ตœ์†Œ ํŽธ์ง‘ ์Šคํฌ๋ฆฝํŠธ"๋ณด๋‹ค "๋น ๋ฅด๊ณ  ํ•ฉ๋ฆฌ์ ์ธ ๋ธํƒ€"๊ฐ€ ๋” ์ค‘์š”ํ•œ ์ž๋ฆฌ์— ๋งž๋Š” ์„ ํƒ์ด๋‹ค.

Myers ๊ณ„์—ด์€ ์™œ ๋”ฐ๋กœ ์žˆ๋‚˜

myersdiff.py, myersdiff_ses.py, myersdiff_dmp.py๋Š” ์ „ํ†ต์ ์ธ Myers O(ND) ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ ๊ตฌํ˜„์ด๋‹ค. ์ด์ชฝ์€ ์ตœ๋‹จ ํŽธ์ง‘ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ˆ˜ํ•™์ ์œผ๋กœ ๋ณด์žฅํ•œ๋‹ค โ€” Git์˜ xdiff๊ฐ€ ์“ฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๊ณ„์—ด์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜.

  • SES ๋ณ€ํ˜•: ํ‘œ์ค€์ ์ธ ์ตœ๋‹จ ํŽธ์ง‘ ์Šคํฌ๋ฆฝํŠธ ๊ณ„์‚ฐ
  • DMP ๋ณ€ํ˜•: Neil Fraser์˜ diff-match-patch ๋ฐฉ์‹๋Œ€๋กœ "middle snake" ์ „๋žต์„ ์“ฐ๋˜ ๋ฆฌ์ŠคํŠธ ์Šฌ๋ผ์ด์‹ฑ ๋Œ€์‹  ์˜คํ”„์…‹์„ ๊ณ„์† ๋„˜๊ฒจ์„œ ๊ณ„์‚ฐํ•œ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ SES์™€ ๊ฐ™์€ ํŽธ์ง‘ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋‚ด์ง€๋งŒ ๊ตฌํ˜„ ๋ฐฉ์‹์ด ๋‹ค๋ฅด๋‹ค.

O(ND) ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ์•ฝ์ ์€ ๋‘ ์ž…๋ ฅ์ด ๋งŽ์ด ๋‹ค๋ฅผ ๋•Œ ์„ฑ๋Šฅ์ด ๋‚˜๋น ์ง„๋‹ค๋Š” ๊ฒƒ์ด๋‹ค โ€” ์ตœ์•…์˜ ๊ฒฝ์šฐ 500KB ์ •๋„ ํฌ๊ธฐ์— 10% ์ •๋„๋งŒ ๋‹ฌ๋ผ๋„ ์ดˆ ๋‹จ์œ„๋กœ ๋А๋ ค์งˆ ์ˆ˜ ์žˆ๋‹ค. StreamSequenceMatcher๊ฐ€ ์ด ์ผ€์ด์Šค์—์„œ ํ›จ์”ฌ ์•ˆ์ „ํ•˜๋‹ค.

Cython ๋ฒ„์ „ (_myersdiff_ses.pyx, _myersdiff_dmp.pyx)

์œ„ ๋‘ Myers ๊ตฌํ˜„์„ Cython์œผ๋กœ ํฌํŒ…ํ•œ ๊ฒƒ์ด๊ณ , ๊ธฐ๋ณธ ๋ฐฑ์—”๋“œ๊ฐ€ ๋ฐ”๋กœ ์ด๊ฒƒ์ด๋‹ค. pyproject.toml์˜ ext-modules์— ๋“ฑ๋ก๋ผ ์žˆ์–ด uv sync ์‹œ ํ•จ๊ป˜ ๋นŒ๋“œ๋˜๋ฉฐ, setup.py๊ฐ€ ์ปดํŒŒ์ผ ์‹คํŒจ๋ฅผ ๊ฒฝ๊ณ ๋กœ ๋‚ฎ์ถ”๋ฏ€๋กœ ํˆด์ฒด์ธ์ด ์—†๋Š” ํ™˜๊ฒฝ์—์„œ๋„ ์„ค์น˜๋Š” ๋œ๋‹ค โ€” ๊ทธ ๊ฒฝ์šฐ StreamSequenceMatcher๋กœ ํด๋ฐฑํ•œ๋‹ค.

์‹ค์ธก

uv run tools/bench_diff.py --size 200 --diff-ratio 0.1 --runs 3 (2,409์ค„, 10% ๋ณ€๊ฒฝ):

์—”์ง„ ์‹œ๊ฐ„ Peak ๋น„๊ณ 
Myers DMP (Cython) 3.3 ms 657 KB ๊ธฐ๋ณธ ๋ฐฑ์—”๋“œ
Myers SES (Cython) 3.9 ms 787 KB
difflib (stdlib) 26.8 ms 338 KB ๋ฒค์น˜๋งˆํฌ ๊ธฐ์ค€์„ 
StreamSequenceMatcher 47.4 ms 1,146 KB ํด๋ฐฑ
Myers DMP (์ˆœ์ˆ˜ ํŒŒ์ด์ฌ) 983.6 ms 660 KB
Myers SES (์ˆœ์ˆ˜ ํŒŒ์ด์ฌ) 990.8 ms 700 KB
Myers (linked-list) 1,295.2 ms 7,101 KB

์ฝ์„ ์  ์…‹์ด๋‹ค.

  • Cython Myers๊ฐ€ ํด๋ฐฑ๋ณด๋‹ค ~14๋ฐฐ ๋น ๋ฅด๋‹ค. ๊ธฐ๋ณธ ๋ฐฑ์—”๋“œ๊ฐ€ ๊ทธ์ชฝ์ธ ์ด์œ ๋‹ค.
  • ๊ฐ™์€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ธ๋ฐ ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ์€ ~300๋ฐฐ ๋А๋ฆฌ๋‹ค (983.6 โ†’ 3.3 ms). ์ด๊ฒŒ myersdiff_*.py๊ฐ€ ํด๋ฐฑ ๋Œ€์ƒ์ด ์•„๋‹Œ ์ด์œ ๊ณ , ์ด ์ž…๋ ฅ์ด O(ND)์— ๋ถˆ๋ฆฌํ•œ ์กฐ๊ฑด(์„œ๋กœ ๋‹ค๋ฅธ ์ค„์ด ๋งŽ์Œ)์ด๋ผ ๊ฒฉ์ฐจ๊ฐ€ ๋” ๋ฒŒ์–ด์ง„๋‹ค.
  • ๋„ค ์—”์ง„ ๋ชจ๋‘ opcode 3๊ฐœ๋กœ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋‚ธ๋‹ค โ€” ์†๋„ ์ฐจ์ด์ง€ ํ’ˆ์งˆ ์ฐจ์ด๊ฐ€ ์•„๋‹ˆ๋‹ค.

์ด ์ˆ˜์น˜๋Š” ์ด ํŒŒ๋ผ๋ฏธํ„ฐยท์ด ๋จธ์‹  ๊ธฐ์ค€์ด๋‹ค. ๋‹ค๋ฅธ ํฌ๊ธฐยท๋ณ€๊ฒฝ๋น„์œจ์—์„œ๋Š” tools/bench_diff.py๋ฅผ ์ง์ ‘ ๋Œ๋ ค ํ™•์ธํ•  ๊ฒƒ.

docs/parser_benchmark.md๋Š” ๋ธ”๋ก ํŒŒ์„œ(regex vs no_regex) ์ด์•ผ๊ธฐ์ด๊ณ  ์—ฌ๊ธฐ diff ์—”์ง„ ๋น„๊ต์™€๋Š” ๋‹ค๋ฅธ ์ธก์ •์ด๋‹ค. ๊ทธ ๋ฌธ์„œ์˜ "Cython 5~10๋ฐฐ"๋Š” ํŒŒ์„œ๋ฅผ ํฌํŒ…ํ–ˆ์„ ๋•Œ์˜ ์ถ”์ •์น˜์ด๋ฉฐ ํŒŒ์„œ ํฌํŒ…์€ ์ฐฉ์ˆ˜๋˜์ง€ ์•Š์•˜๋‹ค.

๊ทธ๋Ÿผ ๋ญ˜ ์จ์•ผ ํ•˜๋‚˜

  • ๊ธฐ๋ณธ๊ฐ’ ๊ทธ๋Œ€๋กœ ๋‘๋ฉด ๋œ๋‹ค. ํˆด์ฒด์ธ์ด ์žˆ์œผ๋ฉด Cython Myers๊ฐ€, ์—†์œผ๋ฉด StreamSequenceMatcher๊ฐ€ ์ž๋™์œผ๋กœ ์žกํžŒ๋‹ค.
  • ํด๋ฐฑ์œผ๋กœ ๋Œ๊ณ  ์žˆ๋Š”๋ฐ ์ปค๋ฐ‹์ด ๋А๋ฆฌ๋‹ค๋ฉด ํ™•์žฅ์ด ๋นŒ๋“œ๋๋Š”์ง€๋ถ€ํ„ฐ ๋ณธ๋‹ค (ACTIVE_BACKEND ํ™•์ธ). SIMPLE_RCS_MATCHER๋กœ ๊ฐ•์ œํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ Myers๋ฅผ ํ”„๋กœ๋•์…˜์— ์“ฐ์ง€ ๋ง ๊ฒƒ โ€” ์ด๋ฆ„์œผ๋กœ ์„ ํƒ์€ ๋˜์ง€๋งŒ ๋ ˆํผ๋Ÿฐ์Šค ๊ตฌํ˜„์ด๊ณ  O(ND) ์ตœ์•…์— ๋…ธ์ถœ๋œ๋‹ค.
  • ์ƒˆ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๋ถ™์ด๋ ค๋ฉด tools/bench_diff.py์— ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด ์‹ค์ธก๋ถ€ํ„ฐ ํ•˜๋Š” ๊ฑธ ๊ถŒํ•œ๋‹ค.

์ง์ ‘ ๋น„๊ตํ•ด๋ณด๊ธฐ

uv run tools/bench_diff.py

์‹œ๊ฐ„๊ณผ ๋ฉ”๋ชจ๋ฆฌ(peak, tracemalloc ๊ธฐ์ค€) ๋‘˜ ๋‹ค ๋น„๊ตํ•ด์ค€๋‹ค. ํŠน์ • ์—”์ง„ ํ•˜๋‚˜๋กœ ๋‘ ๋ฒ„์ „ ์‚ฌ์ด diff๋ฅผ ์ง์ ‘ ๋ณด๊ณ  ์‹ถ์œผ๋ฉด:

uv run tools/srcs_diff.py <ํŒŒ์ผ> --engine ses   # ๋˜๋Š” dmp, pydifflib, myers, difflib

srcs_diff --engine์˜ ์ด๋ฆ„(ses/dmp/...)์€ ์ด ๋„๊ตฌ ์ „์šฉ์ด๊ณ , SIMPLE_RCS_MATCHER๊ฐ€ ๋ฐ›๋Š” ์ด๋ฆ„(ses_cython/dmp_cython/stream/...)๊ณผ๋Š” ๋‹ค๋ฅด๋‹ค. ํ—ท๊ฐˆ๋ฆฌ๋ฉด bench_diff.py๊ฐ€ ์ถœ๋ ฅํ•˜๋Š” id ์ชฝ์ด ํ™˜๊ฒฝ๋ณ€์ˆ˜์šฉ์ด๋‹ค.

Clone this wiki locally