개요
질문은 다음과 같이 구성됩니다.
먼저 내가 이 주제에 관심을 갖는 이유와 이 주제가 내가 진행 중인 문제를 어떻게 해결할 것인지에 대한 배경 지식을 제공합니다. 그런 다음 파일 시스템 캐싱에 대해 실제로 별도의 질문을 하므로 동기(일부 C++ 프로젝트 빌드 설정)에 관심이 없다면 첫 번째 부분을 건너뛰십시오.
원래 질문: 공유 라이브러리 연결
프로젝트 빌드 시간을 단축할 수 있는 방법을 찾고 있습니다. 설정은 다음과 같습니다. 디렉터리(라고 함 workarea
)는 NFS 공유에 있습니다. 처음에는 소스 코드와 makefile만 포함되어 있습니다. 그런 다음 빌드 프로세스에서는 먼저 에서 정적 라이브러리를 생성한 workarea/lib
다음 에서 workarea/dll
정적 라이브러리를 사용하여 공유 라이브러리를 생성합니다 workarea/lib
. 공유 라이브러리를 생성하는 동안 이러한 라이브러리는 기록될 뿐만 아니라 nm
링크 타임에 기호가 손실되지 않았는지 확인하기 위해 다시 읽혀집니다. 많은 작업을 병렬로 사용하면(예: make -j 20 또는 make -j 40) 빌드 시간이 링크 시간에 의해 빠르게 좌우될 수 있습니다. 이 경우 링크 성능은 파일 시스템 성능에 의해 제한됩니다. 예를 들어, 20개의 작업을 병렬로 연결하는 데 NFS 공유에서는 약 35초가 걸리지만 RAM 드라이브에서는 5초만 걸립니다. rsync를 사용하여 NFS 공유로 다시 복사하는 데 dll
6초가 추가로 소요되므로 RAM 드라이브에서 작업한 다음 NFS로 동기화하는 것이 NFS 공유에서 직접 작업하는 것보다 훨씬 빠릅니다. NFS 공유와 RAM 드라이브 간에 파일을 명시적으로 복사/링크하지 않고 빠른 성능을 달성할 수 있는 방법을 찾고 있습니다.
NFS 공유는 이미 캐시를 사용하고 있지만 이 캐시는 읽기 액세스만 캐시할 수 있습니다.
AFAIK, NFS에서는 NFS 서버가 쓰기가 완료되었음을 확인할 때까지 모든 NFS 클라이언트가 쓰기를 승인할 수 없으므로 클라이언트는 로컬 쓰기 버퍼를 사용할 수 없으며 쓰기 처리량(최대 피크 포함)은 네트워크 속도에 따라 제한됩니다. 우리 설정에서는 결합된 쓰기 처리량을 약 80MB/s로 효과적으로 제한합니다.
그러나 읽기 캐싱을 사용하면 읽기 성능이 훨씬 향상됩니다. 생성된 콘텐츠를 NFS에 연결 dll
하고 RAM 드라이브에 심볼릭 링크로 연결하면 성능은 여전히 양호합니다(약 5초). 빌드 프로세스를 완료하려면 NFS 공유에 상주해야 합니다. 빠른 증분 빌드를 허용하려면 공유(또는 영구 마운트)에 있어야 하며, 빌드를 시작하는 시스템에서 액세스할 수 있도록 NFS에 있어야 합니다. 이 DLL을 사용하여 작업합니다. 따라서 아래 문제에 대한 해결책을 적용하고 싶습니다. 이 방법도 효과가 있을 수 있습니다(후자는 컴파일 시간을 단축하는 것입니다). 아래의 빠른 설정 시간에 대한 요구 사항은 필요할 때만 데이터를 복사하여 빠른 증분 빌드를 수행해야 하기 때문입니다.workarea/lib
workarea/dll
workarea/*
lib
dll
workarea/dll
workarea/lib
고쳐 쓰다
빌드 설정에 대해 좀 더 구체적으로 설명해야 합니다. 자세한 내용은 다음과 같습니다. 컴파일 단위는 임시 디렉터리(/tmp)에 있는 .o 파일로 컴파일됩니다. 그런 lib
다음 ar
. 전체 빌드 프로세스는 점진적입니다.
- 컴파일 단위는 컴파일 단위 자체(.C 파일) 또는 포함된 헤더가 변경되는 경우(포함된 컴파일러를 사용하여 생성된 종속성 파일 )
make
만 다시 컴파일됩니다 . - 정적 라이브러리는 해당 컴파일 단위 중 하나가 다시 컴파일될 때만 업데이트됩니다.
- 공유 라이브러리는 정적 라이브러리 중 하나가 변경되는 경우에만 다시 링크됩니다. 공유 라이브러리에 대한 기호는 공유 라이브러리가 의존하는 공유 라이브러리에서 제공하는 기호가 변경되거나 공유 라이브러리 자체가 업데이트되는 경우에만 다시 확인됩니다.
gcc
그럼에도 불구하고 여러 컴파일러( , clang
), 컴파일러 버전, 컴파일 모드( release
, debug
), C++ 표준( C++97
, C++11
) 및 추가 수정 사항(예:)을 사용할 수 있으므로 전체 또는 거의 완전한 재구축이 필요한 경우가 많습니다 . libubsan
모든 조합은 서로 다른 디렉터리를 효과적으로 사용하므로 lib
설정 dll
간에 전환하고 해당 설정의 마지막 빌드를 기반으로 점진적으로 빌드하는 것이 가능합니다. 또한 증분 빌드를 사용하면 일반적으로 몇 개의 파일만 다시 컴파일하면 되며 시간이 거의 걸리지 않지만 공유 라이브러리의 (잠재적으로 큰) 재링크를 트리거하여 훨씬 더 오래 걸립니다.
업데이트 2
그 동안 나는 NFS 마운트 옵션에 대해 배웠습니다 . 이 옵션은 Linux를 제외한 nocto
모든 NFS 구현에서 항상 쓰기 버퍼를 플러시하는 문제를 해결합니다 . 우리는 몇 가지 다른 것을 시도했습니다. 로컬 NFS 서버를 쓰기 버퍼로 활성화하고 기본 NFS 마운트를 내보냅니다. 하지만 이 경우 불행하게도 NFS 서버 자체에는 쓰기 버퍼가 없습니다. 이는 서버가 기본 파일 시스템을 안정적인 저장소로 플러시하지 않고 기본 파일 시스템이 쓰기 버퍼를 사용하는 경우 암시적으로 쓰기 버퍼를 사용한다는 것을 의미하는 것 같습니다(파일 시스템의 경우인 것으로 보입니다) . 드라이브의 실제 위치에서). 우리는 기본 NFS 공유를 마운트하고 , 쓰기 버퍼를 제공하고, 다른 NFS 서버를 통해 이 버퍼링된 마운트를 제공하는 동일한 시스템에서 Linux가 아닌 VM을 사용하는 옵션도 고려했지만 아직 테스트하지는 않았으며 피하길 바랍니다. 그러한 솔루션. 또한 캐시를 기반으로 하는 여러 파일 시스템 래퍼가 캐시로 사용되는 것을 발견했지만 이들 중 어느 것도 쓰기 버퍼링을 구현하지 않았습니다.close()
nocto
async
async
nocto
FUSE
캐시 및 버퍼 디렉터리
orig
NFS 공유와 같이 느린 파일 시스템에 있는 디렉터리(디렉토리라고 부르겠습니다)를 생각해 보세요 . 매우 짧은 시간 동안(몇 초 또는 몇 분 정도는 중요하지 않음) 로컬 하드 드라이브나 메모리 드라이브와 같은 빠른 파일 시스템에 있는 orig
디렉터리를 사용하여 완전히 캐시되고 버퍼링된 보기를 만들고 싶습니다. cache
. 캐시는 마운트 등을 통해 액세스할 수 있어야 하며 cached_view
루트 권한이 필요하지 않습니다. 나는 캐시의 수명 동안 직접 읽기 또는 쓰기 액세스가 없다고 가정합니다 orig
(물론 캐시 자체는 제외). 완전히 캐시되고 버퍼링된다는 것은 다음을 의미합니다.
- 쿼리를 파일 시스템으로 전달하고 , 해당 결과를 캐싱하고, 그 시점부터 사용하여
orig
읽기 쿼리에 응답합니다 . - 쓰기 쿼리는 직접 작성되고
cache
완료 시 승인됩니다. 즉, 캐시도 쓰기 버퍼입니다.close()
이는 파일에 쓸 때 호출되는 경우 에도 발생해야 합니다. 그런 다음 백그라운드에서 쓰기가 로 전달됩니다(아마도 큐를 사용하여)orig
.cache
물론, 작성된 데이터에 대한 읽기 쿼리는 진행 중인 데이터를 사용하여 응답됩니다.
또한 다음이 필요합니다.
- 캐싱은 모든 쌍을 플러시하는 캐싱을 끄는 기능을 제공합니다
orig
. 플러시 실행 시간은 모든 파일이 아니라 기록되는 파일의 크기에 따라 달라집니다. 그 후에는 사람들이 안전하게orig
다시 방문할 수 있습니다. - 설정 시간이 빠릅니다. 예를 들어 캐시 초기화는 파일 크기가
orig
아닌 파일 수 에만 의존할 수 있으므로 한 번orig
복사하는 것은orig
옵션이 아닙니다.cache
마지막으로, 다른 파일 시스템을 캐시로 사용하지 않고 메인 메모리(서버에 충분한 RAM이 있음)에만 캐시하는 솔루션도 원합니다. 내가 아는 한 NFS는 버퍼에 쓰기를 허용하지 않기 때문에 NFS와 같은 내장 캐시를 사용하는 것은 옵션이 아닙니다(1부 참조).
orig
내 설정에서는 to의 내용을 심볼릭 링크한 cache
다음 다음을 사용하여 약간 더 나쁜 동작을 시뮬레이션할 수 있습니다 cache
(모든 쓰기는 실제로 파일을 새 파일로 대체하므로 심볼릭 링크는 버전 교체로 업데이트됩니다). 그런 다음 수정된 파일을 orig
나중에. 이는 위의 요구 사항을 완전히 충족하지 않습니다. 예를 들어 읽기는 한 번만 수행되지 않고 파일이 심볼릭 링크로 대체되며 이는 물론 일부 응용 프로그램에 영향을 미칩니다.
나는 이것이 이 문제를 해결하는 올바른 방법이라고 생각하지 않습니다. (심지어 간단한 설정에서도) 누군가가 더 깨끗하고 빠른 솔루션을 알고 있을 수도 있습니다.
답변1
와, 아직 "overlayfs"라고 대답한 사람이 아무도 없다는 게 놀랍네요.
사실 저는 두 가지 제안을 드리고 싶습니다. 첫 번째는 overlayfs를 사용하는 것인데, 기본적으로 설명하는 내용과 똑같지만 주의할 점이 있습니다. Overlayfs(Linux 3.18 이후 표준)를 사용하면 가상으로 병합된 두 개의 디렉터리 트리에서 데이터를 읽고 그 중 하나만 쓸 수 있습니다. 여러분이 해야 할 일은 빠른 저장소(예: tmpfs)를 선택하여 NFS 볼륨에 덮어쓴 다음 둘의 오버레이 병합에서 컴파일을 수행하는 것입니다. 완료되면 NFS의 모든 파일에는 쓰기가 없으며 다른 파일 시스템은 모든 변경 사항을 보유합니다. 변경 사항을 보존하려면 NFS에 다시 동기화하면 됩니다. 관심 없는 파일을 제외하거나 결과에서 일부 파일을 수동으로 선택할 수도 있습니다.
내 작은 프로젝트에서 상대적으로 간단한 overlayfs 예를 볼 수 있습니다.https://github.com/nrdvana/squash-portage/blob/master/squash-portage.sh 또한 이 스크립트는 오버레이fs 없이 이전 커널을 사용하는 경우 UnionFS를 사용하는 방법을 보여줍니다.
내 경우에는 Gentoo가 수백만 개의 작은 디스크 쓰기를 수행하기 때문에 rsync 명령을 사용하여 소프트웨어 저장소를 업데이트하는 데 매우 오랜 시간이 걸립니다. 나는 overlayfs를 사용하여 tmpfs에 대한 모든 변경 사항을 기록한 다음 mksquashfs를 사용하여 트리의 압축 이미지를 만듭니다. 그런 다음 tmpfs를 덤프하고 그 자리에 압축된 이미지를 마운트했습니다.
두 번째 제안은 "나무에서" 구축하는 것입니다. 아이디어는 소스 코드와 makefile을 하나의 트리에 넣은 다음 첫 번째 트리를 미러링하는 별도의 트리에 모든 중간 파일을 생성하도록 automake에 지시하는 것입니다.
- https://stackoverflow.com/questions/1311231/store-gnu-make-generated-files-elsewhere
- http://voices.canonical.com/jussi.pakkanen/2013/04/16/why-you-should-consider-using-separate-build-directories/
- https://stackoverflow.com/questions/16443854/out-of-tree-build-makefile-without-automake
운이 좋다면 빌드 도구(automake 등)가 이미 이 작업을 수행할 수 있습니다. 운이 좋지 않다면 makefile을 만지작거리며 시간을 보내야 할 수도 있습니다.