먼저 분명히: 이 레포는 진짜로 쓰라고 만든 게 아니다.
리눅스의 FUSE로 진짜로 마운트되고, cp·cat·ls가 다 먹힌다. 농담이지만 코드는 진짜로 돌아간다 — 그게 이 레포의 매력이다.
πfs(파이 파일시스템)의 README는 시종일관 진지한 광고 카피로 농담을 친다. "데이터를 디스크에 저장하느라 공간을 낭비하지 마세요! π는 존재할 수 있는 모든 파일을 품고 있습니다! 다시는 저장공간이 부족할 일이 없습니다!" 논리는 이렇다 — π가 정규수라면 그 자릿수는 고르게 분포하고, 따라서 모든 유한한 숫자 나열은 π 어딘가에 반드시 등장한다. π를 16진수로 보면 모든 가능한 파일이 π 안에 들어 있다는 뜻이다. "그럼 파일을 따로 저장할 게 아니라, π 안에서 그 위치만 찾아 적어두면 되잖아?"
저작권 침해? "그것도 그냥 π의 몇 자리일 뿐인데요. 원래부터 거기 있었어요." README는 끝까지 능청을 떤다. 그런데 핵심 함정은 마지막에 슬쩍 숨어 있다 — "400줄짜리 텍스트 파일 저장하는 데 5분 걸렸어요. 왜 이렇게 느리죠?" 그 답: "아, 이건 초기 프로토타입이라서요. 그리고 무어의 법칙이 있잖아요!" 이 농담의 진짜 교훈이 바로 다음 섹션부터 펼쳐진다.
open/read/write 같은 호출이 들어오면 커널이 당신의 C 함수로 넘겨준다. πfs는 이 FUSE 위에 얹혀, 겉보기엔 평범한 폴더처럼 보이지만 속으로는 π 위치 계산을 한다.쓸모없는데 왜 별이 수천 개나 붙었나.
πfs는 "동작하는 농담(working joke)"의 전설적인 사례다. 주목받는 이유는 세 갈래다. 첫째, 밈으로서 완벽하다 — "100% 압축 달성!", "저작권? π의 몇 자리일 뿐인데요" 같은 카피가 개발자 유머의 정수를 찌른다. 둘째, 교육적 미끼로 탁월하다 — 웃다가 "어? 그런데 왜 안 되지?"를 자문하게 만들고, 그 질문이 정보이론으로 빨려 들어간다. 셋째, 코드가 진짜로 돈다 — 농담이 그냥 README 한 장이 아니라 컴파일·마운트·읽기/쓰기까지 완비된 실제 FUSE 프로그램이라, "농담을 코드로 구현하는 법" 자체가 공부거리다.
| 비교 항목 | 진짜 압축기 (gzip · zstd) | πfs (농담) |
|---|---|---|
| 목표 | 데이터를 더 작게 | 데이터를 정확히 2배로 (역설적 결말) |
| 저장하는 것 | 중복을 줄인 비트열 | π 안에서의 위치 인덱스(메타데이터) |
| 실용성 | 매일 쓰는 핵심 기술 | 0 (쓰면 안 됨) |
| 속도 | 초당 수백 MB | 400줄 저장에 5분(README 자백) |
| 가치 | 실무 도구 | 정보이론을 깨닫게 하는 교보재 |
"모든 파일이 π 안에 있다"는 (정규수라면) 참이다. 문제는 "어디에 있는지"를 가리키는 위치 번호가, 원래 파일만큼 (사실은 더) 크다는 점이다. 100만 자리 파일을 찾으려면 그 위치는 보통 100만 자리 언저리의 거대한 숫자다. 데이터를 없앤 게 아니라 데이터를 '주소'라는 다른 옷으로 갈아입혔을 뿐이다. 이게 4번 섹션 정보이론의 핵심이다.
πfs는 비둘기집 원리(모든 N비트 파일을 N비트 미만으로 압축하는 건 불가능)와 콜모고로프 복잡도(데이터의 진짜 정보량 = 그것을 만들어내는 가장 짧은 프로그램의 길이)를 농담으로 체험시킨다. 정규수, BBP 알고리즘, FUSE까지 — 한 농담 안에 학부 전공 수업 서너 개가 압축돼 있다.
농담이지만 스택은 진짜 견고하다 — C · FUSE · BBP.
전체 코드는 단 두 개의 C 파일이다. πfs.c(약 10KB, FUSE 연동부)와 piqpr8.c(약 2.7KB, π 자릿수 계산부). 빌드는 GNU Autotools 표준 흐름을 그대로 따른다.
| 계층 | 구성요소 | 역할 |
|---|---|---|
| 언어 | C (C99) | AC_PROG_CC_C99 · -Wall -Werror -Wextra로 엄격 컴파일 |
| 파일시스템 계층 | libfuse (FUSE ≥ 2.8, API 26) | 커널 VFS 호출을 사용자 공간 C 함수로 전달. πfs의 뼈대. |
| 수학 핵심 | BBP 알고리즘 + libm(-lm) | π의 임의 위치 16진 자릿수를 앞 자리 계산 없이 바로 뽑음 |
| 빌드 | Autoconf · Automake · pkg-config | ./autogen.sh → ./configure → make |
| 라이선스 | GPLv3 | 소스 자유 배포 |
piqpr8.c는 David H. Bailey가 직접 쓴 BBP 참조 구현으로, get_byte(id)를 호출하면 π의 id 위치에서 16진 두 자리(=1바이트)를 즉시 반환한다. 이 "임의 접근(random access)" 성질이 있어야 πfs의 농담이 코드로 성립한다.보통 책에서 1만 번째 글자를 알려면 처음부터 9,999자를 다 세야 한다. 그런데 BBP 공식은 "책을 처음부터 읽지 않고 1만 번째 글자만 콕 집어 펴 보는 마법의 책갈피" 같은 것이다. πfs는 이 책갈피로 π의 아무 위치나 즉석에서 들춰 본다 — 단, 16진수 책에 한정해서.
코드가 실제로 무엇을 하는가, 그리고 농담의 정보이론적 진실.
README가 그리는 이상은 이렇다 — 파일을 통째로 π 안에서 찾아 (위치, 길이) 한 쌍만 적어두면 끝.
"긴 수열은 π에서 찾기 오래 걸리니, 실용성을 위해 파일을 작은 덩어리로 쪼개자"고 README는 덧붙인다. 그리고 마지막에 폭탄 선언 — "이 구현에서는 성능 극대화를 위해, 파일의 각 바이트를 개별적으로 π에서 찾습니다." 바로 이 한 줄에서 농담의 진실이 드러난다.
실제 πfs.c의 pifs_write를 보면, 파일을 통째로 찾는 게 아니라 한 바이트씩 처리한다. 각 바이트 b에 대해, get_byte(index)가 b를 돌려주는 가장 작은 index를 0부터 SHRT_MAX(32767)까지 선형 탐색한 뒤, 그 2바이트짜리 short 인덱스를 디스크에 적는다.
/* πfs.c — pifs_write (핵심만) */
for (size_t i = 0; i < count; i++) {
short index;
// π의 어느 위치(0~32767)에서 이 바이트가 나오는지 선형 검색
for (index = 0; index < SHRT_MAX; index++) {
if (get_byte(index) == *buf) break; // 찾으면 멈춤
}
write(info->fh, &index, sizeof index); // 1바이트 → 2바이트(short) 저장!
buf++;
}
읽을 때(pifs_read)는 거꾸로, 저장된 short 인덱스를 읽어 get_byte(index)로 π를 재계산해 원래 바이트를 복원한다. 그래서 코드 곳곳에 offset * 2, length * 2, st_size /= 2 같은 "2배" 보정이 깔려 있다 — 메타데이터 디렉터리에는 항상 원본의 정확히 2배 크기가 쌓이기 때문이다.
1바이트(0~255)를 저장하려고 2바이트짜리 short 인덱스를 적는다. 게다가 0~255 어떤 값이든 π의 첫 수백 바이트 안에서 거의 다 찾을 수 있으니, 사실상 "각 바이트를, 그 바이트가 처음 나타나는 π 속 위치 번호로 바꿔치기"하는 1:1 치환일 뿐이다. 압축은커녕 디스크 사용량이 정확히 두 배가 된다. 농담이 코드 레벨에서 스스로를 폭로하는 순간이다.
설령 "바이트 단위"가 아니라 README의 이상대로 "파일 전체를 한 번에" π에서 찾는다 해도, 압축은 수학적으로 불가능하다. 이유는 세 가지 정리로 못 박힌다.
N비트로 표현 가능한 서로 다른 파일은 2^N개다. 이들을 전부 N-1비트 이하로 손실 없이 매핑하려면, 2^N개를 2^N - 1개 이하의 칸에 우겨넣어야 한다. 비둘기집 원리상 반드시 충돌이 생긴다 → 두 파일이 같은 코드로 압축되면 복원이 불가능. 즉 "모든 파일을 더 작게 만드는 압축기"는 존재할 수 없다. πfs도 예외가 아니다.
어떤 데이터의 진짜 정보량은 그것을 출력하는 가장 짧은 프로그램의 길이(콜모고로프 복잡도 K)다. 대부분의 비트열은 압축 불가능(incompressible)하다 — 자기 자신보다 짧게 기술하는 방법이 없다. "π의 위치로 표현"하는 것도 하나의 기술(description) 방식일 뿐이라, 평균적으로 K보다 짧아질 수 없다. π라는 외부 사전을 끌어와도 정보의 총량은 줄지 않는다.
"π는 디스크에 없으니 위치만 적으면 데이터가 사라진 것"처럼 느껴지는 게 농담의 핵심 착시다. 하지만 위치 번호 자체가 데이터와 같은 크기의 정보를 담고 있다. π는 압축 사전 역할조차 못 한다 — 사전(π)이 "모든 것"을 담고 있다는 건, 거꾸로 말하면 그 사전 안에서 무언가를 특정하는 비용이 그 무언가의 정보량과 같다는 뜻이기 때문이다.
도서관에 "상상 가능한 모든 책"이 다 꽂혀 있다고 하자(보르헤스의 바벨의 도서관). "내 소설은 저 도서관에 이미 있으니 안 써도 돼!"라고 외칠 수 있다. 그러나 그 책을 다시 찾으려면 청구기호(위치)를 적어둬야 하는데, 그 도서관이 워낙 방대해서 청구기호 자체가 소설 한 권만큼 길다. 결국 소설을 쓰나, 청구기호를 적나, 종이 양은 똑같다. πfs가 정확히 이 도서관이다.
파일 몇 개 안 되는 미니멀한 농담.
특이점: 실행 파일 이름과 소스 파일 이름에 유니코드 문자 π(U+03C0)를 그대로 사용한다(πfs.c, bin_PROGRAMS=πfs). 농담을 파일명 레벨까지 밀어붙인 디테일이다. 또 README 최상단에는 같은 저자의 후속 농담 inferencefs("LLM이 파일을 추론해 복원하는 무(無)데이터 파일시스템") 링크가 달려 있다.
-o mdd=<디렉터리>로 지정하는 실제 데이터(=π 위치 인덱스)가 쌓이는 진짜 폴더. 마운트 지점은 "π처럼 보이는 가상 폴더"일 뿐이고, 모든 FUSE 콜백은 경로를 mdd + path로 바꿔 진짜 디스크에 읽고 쓴다. 여기에 원본의 2배 크기 데이터가 저장된다 — "데이터가 사라졌다"는 농담의 무대 뒤편.농담 한 편에서 뽑아낼 수 있는 진짜 공부거리.
πfs.c는 사실 잘 짜인 FUSE 패스스루(passthrough) 파일시스템 튜토리얼이다. fuse_operations 구조체에 getattr·read·write·mkdir·readdir·rename·xattr·lock 등 35개 콜백을 빠짐없이 채웠다. 농담을 빼고 보면, "FUSE로 진짜 파일시스템을 어떻게 만드는가"의 깔끔한 예제다. FULL_PATH 매크로로 경로를 mdd로 우회하는 패턴, info->fh에 파일 디스크립터를 실어 나르는 패턴이 특히 배울 만하다.
비둘기집 원리, 콜모고로프 복잡도, 엔트로피(Shannon)를 "왜 πfs는 안 되는가"라는 구체적 질문으로 체득한다. "외부 사전(π)을 끌어와도 정보량은 안 준다"는 직관은 실무 압축·암호·해시 어디서나 통하는 사고틀이다.
piqpr8.c의 series()와 모듈러 거듭제곱 expm()을 읽으면, "왜 BBP로 π의 n번째 16진 자리만 콕 뽑을 수 있는가"를 알게 된다. 모듈러 산술·부동소수점 정밀도 한계(주석에 d < ~1.18×10^7 제한 명시)까지 따라가면 수치해석 감각이 붙는다.
밈을 README 한 장으로 끝내지 않고 컴파일·마운트되는 실제 프로그램으로 만든 태도 자체가 배울 점이다. 농담조차 -Wall -Werror로 엄격하게 빌드한다.
정말로 마운트해 보고 싶다면 (재미로만).
| 구분 | 필요한 것 |
|---|---|
| 운영체제 | Linux (FUSE 지원). macOS는 macFUSE로 가능하나 빌드 손질 필요. |
| 빌드 도구 | autoconf, automake, autotools-dev, pkg-config, C99 컴파일러(gcc/clang) |
| 라이브러리 | libfuse-dev (FUSE ≥ 2.8), libm(표준 수학 라이브러리) |
| 빌드 순서 | ./autogen.sh → ./configure → make → make install |
| 실행 | πfs -o mdd=<메타데이터_폴더> <마운트_지점> |
| 현실 경고 | README 자백 — 400줄 텍스트 저장에 약 5분. 실제 데이터 보관 절대 금지. |
πfs는 학습·재미용 농담이다. 느리고, 용량을 2배로 먹으며, "압축"이라는 주장은 풍자다. 실제 백업·보관에는 절대 쓰지 말 것. 마운트해 본다면 cp로 작은 텍스트 파일 하나 정도만 넣고 mdd 폴더 크기가 2배 되는 걸 눈으로 확인하는 용도로.
난이도별 5단계 — 마운트 체험부터 정보이론 증명까지.
πfs를 빌드해 마운트한 뒤, 1KB짜리 텍스트 파일을 cp로 넣어 본다. mdd 폴더에 쌓인 실제 파일 크기를 ls -l로 재서 원본의 정확히 2배인지 확인한다. "압축"이라더니 왜 2배인지 코드의 sizeof index로 설명해 보라.
piqpr8.c의 get_byte(id)만 떼어 작은 프로그램으로 만들고, id = 0, 1, 2 ...를 넣어 π의 16진 전개(243F6A8885A3...)와 맞는지 대조한다. 주석의 정밀도 한계(d < 1.18×10^7)를 넘기면 결과가 깨지는지도 실험.
/dev/urandom으로 1MB 랜덤 파일을 만들어 gzip·zstd로 압축해 본다. 거의 안 줄어드는 걸 확인하고, 왜 랜덤 데이터는 압축이 안 되는가를 콜모고로프 복잡도로 설명하는 한 문단을 적는다. πfs라면 이 파일도 정확히 2배가 됨을 함께 논증.
πfs.c의 농담 부분(×2 보정·get_byte)을 다 빼고, getattr·readdir·open·read·write만 채운 순수 패스스루 FUSE를 처음부터 작성한다. 그런 다음 read에 "모든 바이트를 대문자로 바꾸기" 같은 변환을 끼워 넣어 πfs의 구조를 체득한다.
"파일을 통째로 π에서 찾아 (offset, length)만 저장하면 압축된다"는 README의 이상을 형식적으로 반박한다. 길이 N 패턴이 π(정규수 가정)에서 처음 등장하는 기대 위치가 약 16^N임을 보이고, 그 offset을 적는 비트 수가 log2(16^N) = 4N비트로 원본 이상임을 증명한다. 비둘기집 원리로 마무리.
5주 코스 — FUSE 실전부터 정보이론·수치해석까지.
| 주차 | 주제 | 실습 · 참고 |
|---|---|---|
| 1주차 | FUSE 기초 — VFS 콜백, 패스스루 파일시스템 | libfuse 예제 · πfs.c의 35개 콜백 읽기 |
| 2주차 | 정보이론 입문 — 엔트로피·접두부호·허프만 | Shannon 엔트로피 손계산 · gzip 동작 관찰 |
| 3주차 | 압축의 한계 — 비둘기집 원리·콜모고로프 복잡도 | "모든 파일 압축 불가" 증명 · 과제 5 |
| 4주차 | BBP 알고리즘과 정규수 — 자릿수 임의 추출 | piqpr8.c 정독 · 모듈러 거듭제곱 직접 구현 |
| 5주차 | "동작하는 농담" 만들기 — 밈을 코드로 | Autotools 빌드 · 후속작 inferencefs 읽기 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| πfs | "데이터를 π의 자릿수 위치로 저장"한다는 농담 FUSE 파일시스템. 실제로는 데이터를 2배로 팽창시킨다. |
| 정규수 (normal number) | 모든 자릿수 묶음이 고르게 등장하는 무리수. π가 정규수인지는 미증명 추측. πfs 농담의 전제. |
| 이접 수열 (disjunctive sequence) | 모든 유한 부분수열을 어딘가 포함하는 무한 수열. 정규수의 한 성질. |
| FUSE | Filesystem in Userspace. 커널 수정 없이 사용자 프로그램으로 파일시스템을 만드는 리눅스 기술. |
| BBP 공식 | π의 n번째 16/2진 자릿수를 앞자리 계산 없이 바로 구하는 공식(1995). piqpr8.c가 구현. |
| 비둘기집 원리 | n+1개를 n칸에 넣으면 충돌 발생. "모든 파일을 더 작게 압축"이 불가능함의 근거. |
| 콜모고로프 복잡도 (K) | 데이터를 출력하는 가장 짧은 프로그램의 길이 = 데이터의 진짜 정보량. 대부분 데이터는 압축 불가능. |
| 엔트로피 (Shannon) | 정보의 평균 불확실성. 무손실 압축의 평균 길이 하한. |
| 오프셋 (offset) | π 안에서 데이터가 시작하는 위치. 데이터만큼 커서 "압축이 안 되는" 원흉. |
| mdd | πfs가 실제 인덱스 데이터를 쌓는 메타데이터 디렉터리. 마운트 지점 뒤의 진짜 저장소. |
| 모듈러 거듭제곱 | expm()이 쓰는 16^p mod ak 계산. BBP 핵심 연산. |
| 패스스루 (passthrough) | 요청을 다른 실제 경로로 그대로 넘기는 파일시스템 패턴. FULL_PATH 매크로가 담당. |