FastSort/src/sort/phase_2.h

551 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
Description: 排序过程的第二阶段
* 第二阶段phase-2
* 1. 打开所有中间文件,并关联上解压线程池
* 2. phase-2-stage-1解压中间文件并进行归并排序放入buf
* 3. phase-2-stage-2多线程进行压缩放入buf
* 4. phase-2-stage-3将buf写入最终的bam文件
* 5. 要注意计算index并写入index文件
Copyright : All right reserved by ICT
Author : Zhang Zhonghai
Date : 2026/02/08
*/
#pragma once
#include <spdlog/spdlog.h>
#include <stdio.h>
#include <string>
#include "phase_1.h"
#include "sam_io.h"
#include "sort.h"
using std::string;
// 循环缓冲区
struct CircularBuffer {
uint8_t* data = nullptr;
size_t readIdx = 0; // 可以读取的开始位置
size_t writeIdx = 0; // 可以写入的开始位置
size_t valueSize = 0; // 有效字节
size_t bufSize = 0; // 缓冲区空间
CircularBuffer() {}
CircularBuffer(size_t initSize) {
bufSize = initSize;
data = (uint8_t*)malloc(bufSize);
}
~CircularBuffer() {
if (data) {
free(data);
data = nullptr;
}
readIdx = 0;
writeIdx = 0;
valueSize = 0;
bufSize = 0;
}
void AllocMem(size_t memSize) { ReAllocMem(memSize); }
void ReAllocMem(size_t memSize) {
if (memSize > bufSize) {
bufSize = memSize;
data = (uint8_t*)realloc(data, bufSize);
}
}
size_t Write(uint8_t* src, size_t len) {
if (len == 0) return 0;
size_t freeSpace = bufSize - valueSize;
size_t toWrite = MIN(len, freeSpace);
if (toWrite == 0) return 0;
size_t firstPart = MIN(toWrite, bufSize - writeIdx);
// 第一段:从 write_index_ 到末尾
memcpy(data + writeIdx, src, firstPart);
// 第二段:如果超过了末尾,则从头部继续
if (toWrite > firstPart) {
memcpy(data, src + firstPart, toWrite - firstPart);
}
writeIdx = (writeIdx + toWrite) % bufSize;
valueSize += toWrite;
return toWrite;
}
size_t Read(uint8_t* out, size_t len) {
if (len == 0) return 0;
size_t toRead = MIN(len, valueSize);
if (toRead == 0) return 0;
size_t firstPart = MIN(toRead, bufSize - readIdx);
memcpy(out, data + readIdx, firstPart);
if (toRead > firstPart) {
std::memcpy(out + firstPart, data, toRead - firstPart);
}
readIdx = (readIdx + toRead) % bufSize;
valueSize -= toRead;
return toRead;
}
// 返回第一个连续空间的大小
size_t FirstPartWriteSize() {
size_t freeSpace = bufSize - valueSize;
size_t firstPart = MIN(freeSpace, bufSize - writeIdx);
return firstPart;
}
// 如果空间不连续,那么返回第二个连续空间的内存大小
size_t SecondPartWriteSize() {
size_t freeSpace = bufSize - valueSize;
size_t firstPart = MIN(freeSpace, bufSize - writeIdx);
if (firstPart == freeSpace)
return 0;
return freeSpace - firstPart;
}
// 跳过不能完整保存一个bam的空间
void SkipWrite(size_t skipBytes) {
valueSize += skipBytes;
writeIdx = (writeIdx + skipBytes) % bufSize;
}
void SkipRead(size_t from, size_t skipBytes) {
if (valueSize <= skipBytes) { // 直接清空
readIdx = 0; // 可以读取的开始位置
writeIdx = 0; // 可以写入的开始位置
valueSize = 0; // 有效字节
} else {
if (from == readIdx) {
readIdx = (readIdx + skipBytes) % bufSize;
valueSize -= skipBytes;
} else {
valueSize -= (bufSize - readIdx + skipBytes);
readIdx = skipBytes;
}
}
}
// 退回一个bam
void RevertRead(size_t from, size_t revertBytes) {
readIdx = from - revertBytes;
valueSize += revertBytes;
}
// 读取一个bam
size_t ReadBam(uint8_t* out, size_t start, size_t len) {
memcpy(out, data + start, len);
valueSize -= len;
if (readIdx != start) {
valueSize -= bufSize - readIdx;
}
readIdx = start + len;
return len;
}
// 是否为空
bool Empty() const { return valueSize == 0; }
// 是否已满
bool Full() const { return valueSize == bufSize; }
// 当前数据量(字节)
size_t Size() const { return valueSize; }
// 剩余可写空间
size_t Free() const { return bufSize - valueSize; }
// 总容量
size_t Capacity() const { return bufSize; }
void Clear() {
readIdx = 0; // 可以读取的开始位置
writeIdx = 0; // 可以写入的开始位置
valueSize = 0; // 有效字节
}
};
// 循环数组
template<class T>
struct CircularArray {
std::vector<T> arr;
size_t readIdx = 0; // 可以读取的开始位置
size_t writeIdx = 0; // 可以写入的开始位置
size_t valueSize = 0; // 有效字节
size_t bufSize = 0; // 缓冲区空间
CircularArray() {}
CircularArray(size_t initSize) {
bufSize = initSize;
arr.resize(initSize);
}
void AllocArr(size_t arrSize) { ReAllocArr(arrSize); }
void ReAllocArr(size_t arrSize) {
if (arrSize > bufSize) {
bufSize = arrSize;
arr.resize(arrSize);
}
}
inline T& operator[](size_t idx) {
return arr[(idx + readIdx) % bufSize];
}
// 推入元素(自动扩容)
void Push(const T& value) {
if (Full()) {
ReAllocArr(bufSize * 1.5);
}
arr[writeIdx] = value;
writeIdx = (writeIdx + 1) % bufSize;
++valueSize;
}
void Push(T&& value) {
if (Full()) {
ReAllocArr(bufSize * 1.5);
}
arr[writeIdx] = std::move(value);
writeIdx = (writeIdx + 1) % bufSize;
++valueSize;
}
// 弹出头部元素(如果为空则返回 nullopt
void Pop() {
if (Empty())
return;
--valueSize;
readIdx = (readIdx + 1) % bufSize;
}
void Revert() {
++valueSize;
readIdx = (readIdx - 1) % bufSize;
}
// 查看头部/尾部(不弹出)
T* Front() {
if (Empty())
return nullptr;
return &arr[readIdx];
}
T* Back() {
if (Empty())
return nullptr;
return &arr[(writeIdx - 1) % bufSize];
}
// 是否为空
bool Empty() const { return valueSize == 0; }
// 是否已满
bool Full() const { return valueSize == bufSize; }
// 当前数据量(字节)
size_t Size() const { return valueSize; }
// 剩余可写空间
size_t Free() const { return bufSize - valueSize; }
// 总容量
size_t Capacity() const { return bufSize; }
void Clear() {
readIdx = 0; // 可以读取的开始位置
writeIdx = 0; // 可以写入的开始位置
valueSize = 0; // 有效字节
}
};
// 合并过程需要的缓冲区
struct Phase2MergeBuffer {
CircularBuffer data;
CircularArray<OneBam> bams;
bool initialized = false;
size_t numProcessed = 0;
OneBam* Front() { return bams.Front(); }
void Pop() {
OneBam* bp = Front();
data.SkipRead(bp->offset, bp->wholeBamLen);
bams.Pop();
}
void Revert() {
bams.Revert();
OneBam* b = bams.Front();
data.RevertRead(b->offset, b->wholeBamLen);
}
size_t Size() { return bams.Size(); }
bool hasSpace(size_t bamLen) { return data.FirstPartWriteSize() >= bamLen; }
// 从一个解压后的block缓冲区拷贝多个bam到循环缓冲区
// 返回实际拷贝的bam数量
size_t CopyBams(DataBuffer &blockBuf, BamArr &arr, int start) {
if (start >= arr.Size()) return 0;
int origStart = start;
size_t numCopied = 0;
OneBam* b1 = &arr.Get(start);
OneBam* b2 = arr.Back();
size_t firstPartSize = data.FirstPartWriteSize();
size_t secondPartSize = data.SecondPartWriteSize();
size_t needSize = b2->offset - b1->offset + b2->wholeBamLen;
int stop = arr.Size();
if (needSize <= firstPartSize) { // 在第一个连续空间里就能放下
// 每个bam的offset需要加上diff以对应新的buf
int64_t diff = (int64_t)data.writeIdx - b1->offset;
data.Write(blockBuf.data + b1->offset, needSize);
for (int i = start; i < stop; ++i) {
bams.Push(arr.Get(i));
bams.Back()->offset += diff;
bams.Back()->addr = data.data;
}
} else {
stop = start;
size_t firstNeedSize = 0;
while (arr.Get(stop).wholeBamLen + firstNeedSize < firstPartSize) {
firstNeedSize += arr.Get(stop).wholeBamLen;
stop += 1;
}
int64_t diff = (int64_t)data.writeIdx - b1->offset;
data.Write(blockBuf.data + b1->offset, firstNeedSize);
for (int i = start; i < stop; ++i) {
bams.Push(arr.Get(i));
bams.Back()->offset += diff;
bams.Back()->addr = data.data;
}
// 跳过first part不能放下完整bam的部分
data.SkipWrite(firstPartSize - firstNeedSize);
b1 = &arr.Get(stop);
start = stop;
// 拷贝第二段
size_t secondNeedSize = needSize - firstNeedSize;
if (secondNeedSize <= secondPartSize) {
stop = arr.Size();
} else {
secondNeedSize = 0;
while (arr.Get(stop).wholeBamLen + secondNeedSize < secondPartSize) {
secondNeedSize += arr.Get(stop).wholeBamLen;
stop += 1;
}
}
diff = (int64_t)data.writeIdx - b1->offset;
data.Write(blockBuf.data + b1->offset, secondNeedSize);
for (int i = start; i < stop; ++i) {
bams.Push(arr.Get(i));
bams.Back()->offset += diff;
bams.Back()->addr = data.data;
}
}
numCopied = stop - origStart;
return numCopied;
}
void InitSize(size_t _bufsize, size_t _bamsize) {
data.AllocMem(_bufsize);
bams.AllocArr(_bamsize);
initialized = true;
}
};
struct BamDataBuffer {
uint64_t offset = 0;
int bamOffset = 0;
DataBuffer block;
BamArr bams;
void Clear() {
offset = 0;
block.Clear();
bams.Clear();
}
};
// 读取和解压需要的buffer
struct Phase2ReadBuffer : ReadBuffer {
FastVector<BamDataBuffer> uncompressBlocks;
int bamNum = 0;
};
// 解析
struct UncompressBuffer {
DataBuffer blockBuf; // 解压的block放在这里
BamArr bamArr; // 解析后的bam数据放在这里
int startIdx = 0;
size_t Size() { return bamArr.Size() - startIdx; }
OneBam *Front() {
if (Size() == 0)
return nullptr;
return &bamArr.Get(startIdx);
}
void Clear() {
startIdx = 0;
blockBuf.Clear();
bamArr.Clear();
}
};
struct Phase2File {
// static constexpr int MID_FILE_BUF_SIZE = 8 * SINGLE_BLOCK_SIZE;
static constexpr int READ_BUF_NUM = 2; // 读入的buf数量
static constexpr int UNCOMPRESSS_BUF_NUM = 2; // 解析的buf数量
static constexpr int COPY_BUF_NUM = 1; // 拷贝到merge缓冲区数量
// 参数
size_t readBufSize = 0;
string fileName;
FILE* fp = nullptr;
Phase2ReadBuffer readData[READ_BUF_NUM];
UncompressBuffer uncompressBuf[UNCOMPRESSS_BUF_NUM];
DataBuffer halfBlock; // 剩余不完整的压缩的block数据
// 读
volatile int readyReadBufNum = 0; // 有几个buffer已经填满可用了, 类似lock_t的作用
uint64_t readOrder = 0; // 当前读取的buf索引
// 解压
volatile int readyUncompressBufNum = 0;
uint64_t uncompressOrder = 0; // 写入的buf索引类似readOrder
bool needUncompress = false;
// 拷贝到归并buf
Phase2MergeBuffer mergeData; // 归并时候用到的数据
uint64_t copyOrder = 0;
volatile int readyMergeBufNum = 0;
volatile int finish = 0; // 读入结束
void Init() {
for (int i = 0; i < READ_BUF_NUM; ++i) {
readData[i].Resize(readBufSize);
}
halfBlock.AllocMem(SINGLE_BLOCK_SIZE);
// 打开中间文件
// spdlog::info("file: {}", fileName);
fp = fopen(fileName.c_str(), "rb");
}
void Init(const string& _fileName, size_t _readBufSize) {
readBufSize = _readBufSize;
fileName = _fileName;
Init();
}
// 缓冲区大小应该根据中间文件的数量进行调整
Phase2File() {}
Phase2File(const string &_fileName, size_t _readBufSize) {
readBufSize = _readBufSize;
fileName = _fileName;
}
~Phase2File() {
if (fp) {
fclose(fp);
}
}
};
// 用于合并压缩的数据结构
struct Phase2MergeData {
vector<DataBuffer> blockDataArr; // 待压缩的数据
vector<DataBuffer> compressDataArr; // 压缩后的数据
void Resize(int blockNum) {
blockDataArr.resize(blockNum);
compressDataArr.resize(blockNum);
}
};
/* 第二阶段的多线程流水线参数 */
struct Phase2PipelineArg {
// common parameters
static const int MERGE_BUF_NUM = 2; // 压缩的buf数量
static const int COMPRESS_BUF_NUM = 2; // 压缩的buf数量
int compressBlocksThreshold = 0; // 进行归并排序的单次block数量阈值参数
int numThread = 0; // 线程数
int compressLevel = 3; // 压缩级别,参数
uint64_t numBam = 0;
// 从phase1转移来的数据
UncompressBlockBuffer& uncompressData; // 所有线程共用一个串行往这里添加解压后的block数据
BamArr& allBams; // 所有线程共用一个串行往这里添加解析后的bam数据
// 读入中间文件
vector<Phase2File> midFiles;
yarn::lock_t* readSig;
uint64_t readOrder = 0;
volatile int readFinish = 0;
// 解压读入的blocks
yarn::lock_t* uncompressSig;
uint64_t uncompressOrder = 0;
volatile int uncompressFinish = 0;
int curBlockNum = 0;
// 拷贝到merge缓冲区
yarn::lock_t* copyMerge;
uint64_t copyMergeOrder = 0;
int uncompressReadyNum = 0;
volatile int copyMergeFinish = 0;
// 将解压的数据拷贝进merge的缓冲区归并后放入以便后续压缩用到
MergeCompressData mergeData[MERGE_BUF_NUM];
yarn::lock_t* mergeSig;
yarn::lock_t* copyCompressSig;
uint64_t mergeOrder = 0; // 前半段
uint64_t copyCompressOrder = 0; // 后半段,需要压缩
volatile int mergeFinish = 0;
volatile int copyCompressFinish = 0;
// 压缩
DataBuffer compressBuf[COMPRESS_BUF_NUM]; // 压缩用到的buf
yarn::lock_t* compressSig;
uint64_t compressOrder = 0;
volatile int compressFinish = 0;
// 写入文件
FILE* outFilePtr = nullptr; // 文件指针
uint64_t writeOrder = 0;
void SetThreadNum(int nThread) {
numThread = nThread;
// const int kThres = 1024;
const int kThres = 2048;
int threadBlocksThres = numThread * 32;
compressBlocksThreshold = threadBlocksThres < kThres ? threadBlocksThres : kThres; // 大概64M的解压缩后的数据
for (int i = 0; i < COMPRESS_BUF_NUM; ++i) {
mergeData[i].Resize(compressBlocksThreshold);
}
}
Phase2PipelineArg(UncompressBlockBuffer& lastBuf, BamArr& lastBams, string &midFilePrefix, int midFileNum, int threadNum) : uncompressData(lastBuf), allBams(lastBams){
SetThreadNum(threadNum);
int allFileBufSize = 64L * 1024 * 1024;
// int bufBlocks = (numThread * 4 + midFileNum) / (midFileNum + 1);
// size_t kFileBufSize = bufBlocks * SINGLE_BLOCK_SIZE;
size_t kFileBufSize = allFileBufSize / (midFileNum + 1);
spdlog::info("mid file buf size: {}", kFileBufSize);
midFiles.resize(midFileNum);
for (int i = 0; i < midFileNum; ++i) {
midFiles[i].Init(midFilePrefix + std::to_string(i), kFileBufSize);
}
readSig = yarn::NEW_LOCK(0);
uncompressSig = yarn::NEW_LOCK(0);
copyMerge = yarn::NEW_LOCK(0);
mergeSig = yarn::NEW_LOCK(0);
copyCompressSig = yarn::NEW_LOCK(0);
compressSig = yarn::NEW_LOCK(0);
}
};
void phase2Pipeline(Phase2PipelineArg &p);