커널 레벨에서 SquashFS 최적화(Android 9까지)

SquashFS는 Linux용으로 압축된 읽기 전용 파일 시스템입니다. 파일 시스템은 읽기 전용으로 설계되어 시스템 파티션에서 사용하기에 적합합니다. 다수의 Android 기기에서는 시스템 파티션에 이러한 파일 시스템을 사용해 이점을 얻을 수 있는데, 이러한 기기의 예는 다음과 같습니다.

  • Android Watch와 같이 저장용량이 적은 기기
  • 플래시 저장소가 느린 기기(압축하면 블록 I/O 수가 줄어듦)

하지만 SquashFS의 성능은 ext4보다 뒤쳐집니다.

최적화

SquashFS의 성능을 개선하기 위해 다음 최적화가 구현되었습니다.

메모리 사용량 및 memcpy 줄이기

블록(기본값 128K)을 읽을 때 SquashFS는 이 블록을 덮고 있는 모든 페이지를 가져오려고 합니다.

단일 페이지가 최신 상태이거나 잠겨 있는 경우에는 되돌아가 전체 블록을 할당하고 읽기 요청을 제출한 다음 콘텐츠를 페이지에 복사합니다.

이 방법은 매우 비효율적인데, 어느 정도 시간이 흐른 후 인접 페이지가 최신 상태가 아닌 경우에도 페이지 캐시에 최신 상태인 페이지가 포함될 수 있습니다.

이제 코드가 구멍(= 누락된 페이지)이 있는 블록을 처리할 수 있습니다. 이렇게 하면 다음과 같은 방법으로 성능이 개선됩니다.

  • memcpy 호출 횟수를 줄입니다.
  • 메모리 할당을 줄입니다.

비동기 읽기

SquashFS는 여전히 지원 중단된 ll_rw_block() 함수를 사용합니다. 이러한 접근 방법에는 다음과 같은 두 가지 문제가 있습니다.

  • 이름에서 알 수 있듯이 이 함수는 읽기가 완료될 때까지 기다린 후에 반환합니다. 이는 .readpage()가 이미 페이지의 잠금을 기다리고 있기 때문에 불필요합니다. 게다가 .readpages()를 효율적으로 구현하기 위해 비동기 메커니즘이 필요합니다.
  • 읽기 요청의 완전한 병합은 I/O 스케줄러에 따라 달라집니다. ll_rw_block()은 단순히 버퍼 당 요청을 하나만 생성합니다. SquashFS에는 I/O 스케줄러보다 병합 대상에 대한 정보가 더 많이 있습니다. 또한 요청을 병합하면 I/O 스케줄러에 대한 의존도가 떨어집니다.

이러한 이유로 ll_rw_block() 함수는 submit_bio()로 대체되었습니다.

Readpages(미리 가져오기)

SquashFS는 .readpages()를 구현하지 않아서 커널이 반복적으로 .readpage()를 호출합니다.

읽기 요청은 비동기식이므로 커널은 비동기 미리 읽기 메커니즘을 사용하여 페이지를 미리 가져올 수 있습니다.

압축되지 않은 블록 읽기 최적화

Android와 같은 최신 시스템에는 이미 압축된 많은 파일이 포함되어 있습니다. 따라서 이미지에 압축할 수없는 블록이 많이 포함되어 있습니다.

SquashFS는 동일한 로직을 사용하여 압축된 블록과 압축되지 않은 블록을 처리하고, 단일 페이지를 읽으라는 메시지가 표시되면 전체 블록(기본값: 128k)을 읽습니다. 이러한 방식은 압축된 블록에는 필수적이지만 압축되지 않은 블록에는 리소스 낭비일 뿐입니다.

이제 SquashFS에서는 전체 블록을 읽는 대신 readahead 알고리즘이 제공하는 정보만 읽습니다.

이렇게 하면 임의 읽기의 성능이 크게 향상됩니다.

코드

SquashFS 코드 최적화는 AOSP에서 사용할 수 있습니다.