我正在开发一个处理多通道音频处理的Android应用。 STFT函数以3D复杂数组的形式生成复杂的频率-时间表示,其维度为nChannels,nFrames,nFreq。
但是,在下一步中,我需要执行盲源分离,因为我将每个频率仓的通道和帧移动到矩阵中后,运行时间会大大受益。当前,在读取STFTin的条目时,该代码相当不适合缓存。有什么方法可以使其对缓存更友好?
raw_documents
答案 0 :(得分:1)
正如我在评论中提到的那样,您可能需要改编现有的对缓存友好的矩阵转置算法以实现您的逻辑(因为这是您正在做的大部分操作-矩阵转置)。其他人已经充实了有关最佳块大小,循环顺序以及如何考虑边缘情况(例如不规则形状的矩阵)的实现细节,因此,对现有代码进行调整将同时带来更快和更少错误的代码。
但是,如果您确实需要推出自己的解决方案,这是我会尝试的方法。首先,对循环进行重新排序,使频率和通道循环位于框架for循环内。您是直接从频率子数组访问元素并将它们存储在通道子数组中,因此,在将它们加载到高速缓存中时充分利用这两个元素非常重要。这就是为什么我们需要保持框架在外面循环。
接下来,根据与缓存大小成一定比例对数组访问进行分块。这样,我们操作的所有频率和通道子阵列都可以同时存在于缓存中-我们不会通过一次从频率阵列中读取太多值来消除通道子阵列,反之亦然。有很多方法可以计算出这个大小,但是说实话,运行和计时它更可靠,更快捷-当事情不再变得越来越快时,就不再增加块大小。
下面的大致代码概述:
Complex[][][] temp = new Complex[nFreqs][nFrames][nChannels];
Complex[][][] tempConj = new Complex[nFreqs][nFrames][nChannels];
int blockSizeF = 1 << 2; // Increase these until you see no speedup
int blockSizeC = 1 << 3;
X = new Array2DRowFieldMatrix[nFreqs];
Xcopy = new Array2DRowFieldMatrix[nFreqs];
Xconj = new Array2DRowFieldMatrix[nFreqs];
Y = new Array2DRowFieldMatrix[nFreqs];
for (int t = 0; t < this.nFrames; t++) {
for (int fBlock = 0; fBlock < nFreqs; fBlock += blockSizeF) {
for (int cBlock = 0; cBlock < this.nChannels; cBlock += blockSizeC) {
for (int f = fBlock; f < fBlock + blockSizeF; f++) {
for (int c = cBlock; c < cBlock + blockSizeC; c++) {
temp[f][t][c] = STFTin[c][t][f];
tempConj[f][t][c] = STFTin[c][t][f].conjugate();
//STFTin is nChannels by nFrames by nFreq
}
}
}
}
}
for (int f = 0; f < nFreqs; f++) {
X[f] = new Array2DRowFieldMatrix<>(temp[f]);
Xconj[f] = new Array2DRowFieldMatrix<>(tempConj[f]);
Xcopy[f] = (Array2DRowFieldMatrix<Complex>) X[f].copy();
Y[f] = (Array2DRowFieldMatrix<Complex>) X[f].copy();
}
通常blockSizeF
和blockSizeC
是相同的,但是在这种情况下,每读一次STFTin
的频率子数组,就需要对{{ 1}}和temp
。这意味着您需要通道的块大小大于频率-可能是tempConj
的一个因素,也许是2
的一个因素-我不太确定。我以为它会是这两者之一,所以我只是尝试一下,找出最有效的方法。无论哪种方式,您都可能希望将块大小四舍五入为最接近的2的幂(或至少是2的大幂的倍数),以帮助与缓存行或页面边界对齐。
但是请注意,sqrt(2)
和blockSizeF
绝对必须分别是blockSizeC
和nFreqs
的因数。围绕此规定有很多方法,但是它复杂,缓慢且容易出错。通常,仅填充矩阵并在转换后去除多余的部分通常会更容易。