2P by GN⁺ 2일전 | ★ favorite | 댓글 1개
  • lsr은 io_uring 기반 IO 라이브러리 ourio를 활용해 개발된 새로운 ls(1) 대체 프로그램
  • 기존 ls 및 대안 도구(eza, lsd, uutils ls)에 비해 명령 실행 속도가 매우 빠르며, 시스템 콜 수도 10배 이상 적음
  • 디렉토리 오픈, stat, lstat 등 모든 주요 IO를 io_uring으로 비동기·배치 처리하여 성능 극대화. 파일이 많을수록 더 빠름
  • Zig의 StackFallbackAllocator를 활용해 메모리 할당 시 mmap 호출을 최소화함
  • 동적 링킹 없이 정적으로 빌드해 실행 파일 크기까지 기존 ls보다 더 작음

소개 및 의의

  • lsr 프로젝트는 일반 ls 명령어의 대체제로서 io_uring을 활용하는 빠른 디렉토리 리스팅 도구
  • 기존의 ls, eza, lsd, uutils ls와 비교해, 실행 속도시스템 콜 사용량에서 탁월한 성능을 보임
  • 직접 개발한 io 라이브러리(ourio)로 최대한 많은 IO를 직접 수행
  • 벤치마크를 통해 lsr이 대규모 파일환경에서도 빠른 성능과 품질을 증명함

벤치마크 결과

  • hyperfine을 사용해 n개의 일반 파일이 있는 디렉토리에서 각 명령의 실행 시간을 측정
    • lsr -al 10–10,000개 파일 기준, 기존 ls/대체제 대비 월등히 짧은 실행 시간을 기록함
    • 예: 10,000개 파일에서 lsr이 22.1ms, 기존 ls(38.0ms), eza(40.2ms), lsd(153.4ms), uutils ls(89.6ms) 대비 최고의 속도를 기록
  • 시스템 콜 집계는 strace -c로 진행
    • lsr -al: 최소 20회(n=10)부터 최대 848회 (n=10,000)로 매우 낮은 콜 수 유지
    • ls는 최대 30,396회(n=10,000), lsd가 100,512회 등 나머지 대체제도 수천~십만 회 수준임
    • 동일 조건에서 lsr이 최소 10배 이상 적은 syscall 수로 최고의 효율 달성

lsr의 구조와 구현 방식

  • 프로그램은 인자 파싱, 데이터 수집, 데이터 출력의 3단계로 동작
  • 모든 IO는 두 번째 데이터 수집 단계에서 발생하며, 가능한 모든 파일 접근/정보 조회를 io_uring으로 처리
    • 타겟 디렉토리 오픈, stat, lstat, 시간/유저/그룹 정보 조회 모두 io_uring 기반으로 수행
    • stat을 배치 처리하여 시스템 콜 수를 획기적으로 줄임
  • Zig StackFallbackAllocator로 메모리 1MB를 사전 할당해 mmap 등 추가 시스템 콜을 최소화

정적 빌드와 최적화

  • libc 동적 링킹 없이 완전 정적 빌드이므로 실행 오버헤드가 현저히 적음
  • GNU ls 대비 lsr의 ReleaseSmall 빌드 사이즈가 138.7KB vs 79.3KB로 더 작음
  • 단, lsr은 로케일(언어/지역) 지원이 없음. 일반 ls는 다양한 언어 지원을 위해 오버헤드 발생

시스템 콜 및 성능 이슈 분석

  • lsd는 파일당 clock_gettime을 5번 이상 호출, 그 이유는 불명확(내부 타이밍 측정 등 추정)
  • 정렬(sorting) 작업이 전체 작업 중 상당 부분(약 30%)을 차지함
    • uutils ls는 시스템 콜 효율은 높으나 정렬 처리에서 느려짐
  • io_uring 도입만으로도 서버 등 고부하 IO 환경에서 혁신적 성능 향상 가능성 확인

결론

  • 개발 시간도 오래 걸리지 않고, syscall 최적화 효과가 기대 이상임
  • lsr은 빠른 속도, 적은 시스템 콜, 간결한 크기를 동시에 달성하는 실험적 ls 대체제임
  • 대용량 파일 환경이나 고성능 IO가 중요한 시스템에 매우 적합
  • locale 미지원 등 일부 기능 한계가 있지만, 실무와 벤치마크 모두에서 혁신적인 결과를 보임
Hacker News 의견
  • 프로젝트의 작성자임을 밝힘과 동시에, io_uring 기반 lsr에 대한 소개글을 여기에서 확인 가능함

    • Sun에서 I18N 프로젝트를 했던 경험을 공유함. 여러 환경(지역화, utf8 등)을 지원하려면 프로그램에 다양한 처리를 추가해야 하므로 결과를 내는 데 드는 비용과 속도가 반비례함을 체감했음. 본래 UNIX의 ls(1)은 단순 설계에 굉장히 빨랐지만, 다양한 기능 추가 및 가상 파일 시스템(VFS), 다양한 문자셋, 컬러 지원 등 작은 비용이 계속 누적되어 느려짐. io_uring이 다루는 추상화 비용에 대해 재밌는 논의라고 생각함
    • bfs 프로젝트도 io_uring을 사용함 (소스 코드 링크). lsr와 bfs -ls의 성능 비교가 궁금해짐. 현재 bfs는 멀티스레딩 시에만 io_uring을 쓰고 있지만 single thread(bfs -j1)에도 활용할지 고민해볼 만함
    • tim (소개 링크)을 통해 시간 측정하면 hyperfine보다 더 나을 것 같음. Nim으로 작성되어 도전일 수 있지만 이름이 비슷한 게 우연치고는 재밌음
    • C++ 프로젝트를 Zig로 포팅하는 것을 염두에 두고 있음. 직접 만든 ‘libevring’도 아직 초기라, 필요하면 ourio로 대체 가능하다는 열려있는 마음임. Zig 기반 프로젝트에 C/C++ 바인딩 지원이 있으면 C/C++에서 Zig로 이주할 때 유용하리란 생각임
    • 해당 소개글이 배경 설명이 더 잘 되어 있으니 메인 링크로 삼고, 레포 스레드는 위에 추가해둘 계획임
  • NFS 서버에서 (특히 좋지 못한 네트워크 환경에서) lsr의 성능이 어떨지 궁금함. 불안정한 네트워크 서비스에 blocking POSIX syscall을 사용하는 게 NFS 설계의 단점임이 자명. io_uring이 이런 문제를 얼마나 완화할 수 있는지도 관찰 포인트임

    • NFS 설계자는 분산 시스템을 하드 드라이브처럼 매우 일관되게 동작하게 구현했음. 기존 툴(ls 등)이 네트워크 오류 상황을 직접 처리할 필요가 없던 건 장점이었음. 원래 NFS 프로토콜은 상태를 저장하지 않아 서버 재부팅에도 클라이언트가 자동 복구됨. io_uring이 이런 케이스에서 에러를 제대로 넘겨주는지가 궁금함. NFS 타임아웃 시 어떤 방식으로 처리되는지도 관심사임
    • 집에서 여러 대의 PC에서 NFS $HOME을 사용하는데, 네트워크가 좋고 병렬 쓰기 같은 어려운 케이스만 피하면 NFS의 평균적인 사용성은 꽤 만족스러움. 단, 네트워크 케이블이 불안정했을 때 끊김 현상 때문에 힘든 적은 있었음
    • NFS 폴더를 읽고 있는 앱에서 ctrl+c가 안 먹히는 상황은 잘 알려진 불편함임. 이론적으로는 intr 마운트 옵션이 행중인 원격 서버에서의 오퍼레이션에 시그널을 전달해 중단 가능하도록 지원했으나, 리눅스에선 이미 오래 전에(현재는 soft 옵션만 가능) 제거되었음(참고1, 참고2(FreeBSD 지원))
    • Samba도 이와 비슷한 이슈가 있음
  • syscall 호출 수를 35배 줄였는데도 속도 개선은 2배 정도라는 점이 흥미로움

    • 대부분의 syscall이 VDSO를 통해 이뤄지기 때문에 큰 비용이 들지 않음
    • 예전에 읽었던 io_uring 관련 벤치마크에서, io_uring 기반 syscall이 기존 syscall보다 무거운 것으로 나오기도 했었음. 그렇다고 해도 체감상 상당히 큰 개선임. 정확한 출처는 생각이 안 나지만 인상적으로 남아있음
  • io_uring 활용 사례로서 기대했던 장기적인 속도 이득, 혹은 튜토리얼 사용법 소개로 더 관심이 가는 프로젝트임. 기존 eza같은 툴 대비 왜 이것이 필요한지 체감적 동기부여가 없었음. 파일 만 개 리스트의 실행이 40ms vs 20ms라면, 단일 실행 기준으로는 전혀 차이를 느끼지 못할 것 같음

    • 재미 삼아 io_uring 사용법을 익히고 싶어 만든 실험적 프로젝트임. 실질적으로 얻는 시간 절감 효과는 미미하고(살면서 5초 절약 수준), 이게 핵심 포인트는 아니었음
    • 실제로 수백만 개의 JSON 파일이 들어있는 디렉토리에서는 ls/du 실행에 수 분이 소요됨. coreutils 기본 명령어는 최신 SSD 성능을 제대로 활용하지 못하는 경우가 많음
  • lsr도 좋지만 컬러링 및 아이콘 지원은 eza가 더 우수함. 본인은 "eza --icons=always -1"로 세팅하고 있어서, 음악 파일(.opus 등)은 알아서 아이콘과 컬러로 표시되는데 lsr에서는 그냥 평범한 파일로 표시됨. 그래도 lsr은 패치도 쉽고 엄청 빠르다는 점은 확실하게 느낌. 더불어 cat이랑 다른 유틸리티도 이런식으로 만들어주면 좋겠다는 기대, tangled.sh 및 atproto 사용도 신기하게 여김. zig로 작성되어 있어 rust보다 초보 입장에서 더 쉽게 느껴짐

    • “bat”는 현대적인 “cat” 대체제임 (bat 바로가기)
    • 컬러링 지원은 LS_COLORS/dircolors처럼 표준 방식을 구현하는 게 제일 좋을 것 같음. GNU ls는 컬러가 예쁘게 나옴
  • 왜 모든 CLI 툴들이 io_uring을 사용하지 않는지 궁금했음. 본인은 nvme를 usb 3.2 gen2에 연결할 때, 평범한 툴로 속도가 740MB/s인데 aio나 io_uring 기반 툴로는 1005MB/s까지 올라감. 큐 길이 전략이나 lock 감소 효과도 있다고 봄

    • 포터블 구축을 위해 전통적으로 #ifdef 같은 매크로 분기 없이 짰기 때문에 플랫폼/버전 전용 신기술 도입이 느림. 이제 와선 다양한 posixy 플랫폼 간 호환성의 이점은 예전만 못하다고 생각함
    • io_uring을 효율적으로 쓰려면 비동기 이벤트 기반 모델이 필요함. 기존 CLI 툴 대부분은 직관적이고 순차적으로 작성되어 있음. 언어 차원에서 async가 자연스럽게 쓰인다면 더 포팅이 쉬웠겠지만, 지금은 큰 리팩터링이 필요함. io_uring도 완전히 안정화된 건 아니어서, 새로운 기술의 등장까지 지켜보고 자동 포팅 툴/AI 등이 나올 수도 있다고 생각함
    • io_uring은 도입 초기 주요 보안 이슈가 있었음(2년 전쯤). 지금은 상당수 해결됐지만 보급에는 악영향을 미쳤음
    • io_uring이 보안 측면에서 매우 어려움
    • io_uring이 워낙 최신 기술이라 coreutils(그리고 선행 패키지)는 수십 년 된 전통이 있고, io_uring 도입까지 시간이 더 걸릴 거임. “공유 링 버퍼” 방식의 시스템 콜이 기존 동기 방식 대신 표준이 되려면 시간이 필요함
  • lsd가 파일 하나당 clock_gettime을 5번쯤 호출하는 현상이 strace로 관찰됨. 원인을 정확히 모르겠으며, 아마 각 시간스탬프별 “몇 분/시간/일 전”을 계산하거나, 라이브러리 레거시일 수도 있음

    • 요즘 clock_gettime은 진짜 syscall이 아니라 vDSO를 통해 처리됨(man 7 vDSO 참고). 혹시 zig가 이 구조를 활용하지 않는 게 아닐까 하는 생각임
  • 약간 주제 이탈일 수 있지만, Mellanox 4 또는 5 등 고엔터프라이즈 서버에서 10G NIC환경 하에서 io_uring이 LD_PRELOAD 대비 소켓 지연 오버헤드를 마이크로초 단위로 얼마나 줄여주는지 실제 경험치나 벤치마크 수치가 궁금함. 서로 효과가 누적되지 않는 듯하며, 직접적인 경험이 있다면 수치를 듣고 싶음

  • io_uring은 getdents를 지원하지 않아 주효한 이점은 bulk stat(예: ls -l)으로 나타남. getdents 처리를 비동기화하고 중복 처리할 수 있다면 좋겠다는 아쉬움 있음

    • POSIX에서 NFS의 “readdirplus”(getdents + stat) 연산을 표준화하면 io_uring만의 이점 일부가 상쇄될 것으로 예상함
  • .mjs와 .cjs 확장자에 대한 아이콘은 있지만, .c, .h, .sh와 같은 파일 확장자에는 없는 점이 재밌게 느껴짐