性能优化:将浮点数转换为整数并将结果剪裁到指定范围内

3

我正在使用浮点数进行音频处理。结果需要转换回PCM样本,但是我发现从浮点数转换为整数的强制类型转换非常耗费时间。更加令人沮丧的是,我需要将结果剪切到短范围内(-32768至32767)。 虽然我通常会本能地认为这可以通过将浮点数转换为短整型来实现,但在Java中,这种方法失败了,因为在字节码级别上,它会产生F2I后跟I2S的结果。因此,不是简单的:

int sample = (short) flotVal;

我不得不使用这个丑陋的序列:

int sample = (int) floatVal;
if (sample > 32767) {
    sample = 32767;
} else if (sample < -32768) {
    sample = -32768;
}

有没有更快的方法来做这件事?

(大约总运行时间的6%似乎花费在转换上,虽然乍一看6%并不算太多,但当考虑到处理部分涉及大量矩阵乘法和IDCT时,这是令人震惊的)

  • 编辑:上面的转换/剪辑代码(毫不奇怪)位于一个循环体中,该循环体从float[]读取浮点值并将它们放入byte[]中。我有一个测试套件,可以在几个测试用例上测量总运行时间(处理大约200MB的原始音频数据)。当将强制转换赋值“int sample =(int)floatVal”替换为将循环索引分配给样本时,从运行时差异中得出了6%。

  • 编辑@leopoldkot:我知道Java中的截断,如原始问题所述(F2I,I2S字节码序列)。我之所以尝试将其转换为短整型,是因为我假设Java具有F2S字节码,但不幸的是它没有(最初来自68K汇编背景,其中简单的“fmove.w FP0,D0”将完全符合我的要求)。


你是如何得出你花费了6%的时间在转换上的?这在你的分析输出中如何显示? - Amir Afghani
你必须从浮点数开始吗?你能否从固定小数开始呢? - Mike Dunlavey
转换为全整数代码可能是可能的,但这将意味着进行重大改写(几千行代码),并且为了实现可接受的精度,必须使用 long 进行定点计算。这将使内存带宽要求翻倍,并且 32 位机器在所有 long 操作上都会遭受额外的惩罚(因为它们必须使用多个 32 位指令)。基于这种前景,我不想尝试。 - Durandal
5个回答

2

如果值在范围内,您可以将两个比较合并为一个。这样可以将成本减半。目前,如果值太小,您只执行一次比较。(这可能不是您的典型情况)

if (sample + 0x7fff8000 < 0x7fff0000)
    sample = sample < 0 ? -32768 : 32767;

你的假设是正确的,在绝大多数情况下,样本都在范围内(>99.9%)。因此,少一点判断分支可以帮助提高性能,但只有一点点。我很喜欢你的方法如何使用整数溢出来隐式地检查上限。我一定会记住这个技巧。 - Durandal

1
这是Python,但应该很容易转换。我不知道浮点运算的成本如何,但如果您可以将其保留在整数寄存器中,可能会有一些提升;这假设您可以将IEEE754位重新解释为int。(这就是我的名字不好的float2hex正在做的事情。)
import struct

def float2hex(v):
    s = struct.pack('f', v)
    h = struct.unpack('I', s)[0]
    return h

def ToInt(f):
    h = float2hex(f)
    s = h >> 31
    exp = h >> 23 & 0xFF
    mantissa = h & 0x7FFFFF
    exp = exp - 126
    if exp >= 16:
        if s:
            v = -32768
        else:
            v = 32767
    elif exp < 0:
        v = 0
    else:
        v = mantissa | (1 << 23)
        exp -= 24
        if exp > 0:
            v = v << exp
        elif exp < 0:
            v = v >> -exp

        if s:
            v = -v

    print v

这个分支可能会让你崩溃,但也许它提供了一些有用的东西?这将向零舍入。


我提供这部分内容是因为你可能能够从指数检查中获得一些巧妙的操作。此外,如果您的编程语言的右移运算符执行符号扩展,您可以直接将v乘以(s&1),而不必在最后进行s检查。 - dash-tom-bang
通过巧妙的操作,您可以检查 (float_bits & 0x7F800000) > ((126 + 16) << 23) 来彻底混淆您的代码;如果该检查通过,则知道您超出了范围,并可以执行可能较慢的一系列检查来确定溢出的符号,因为如果指数大于此值,则无论符号位如何,都超出了范围。 - dash-tom-bang
我曾经想过手动执行整数转换,但我不知道该怎么做。在Java中将浮点原始位转换为int需要调用本地方法(Float.floatToRawIntBits),并且结合所需的广泛检查,比接受答案的强制转换+比较要慢得多。顺便说一下:在Java中可以简单地使用h <0测试符号,因为int是有符号的。这消除了变量s的需要(在Phyton中可能不是一个选项)。 - Durandal

1

当你将int转换为short时,你永远不会得到剪辑功能,位被截断,然后被读取为short。 例如:(short)-40000变成25536,而不是你期望的-32768。

可能你需要编辑你的问题,如果你反汇编了字节码,我相信你知道这一点。 此外,有一个JIT编译器,它可能会将这段代码优化(因为它经常被调用)为平台相关的指令。

请将此答案转换为评论。


0

将浮点数转换为整数是在x86处理器上可以执行的最慢的操作之一,因为它需要修改x87舍入模式(两次),这会使处理器串行化并刷新。如果您可以使用SSE指令而不是x87指令,则可以获得相当大的加速效果,但我不知道在Java中是否有办法做到这一点。也许尝试使用x86_64 JVM?


-1

int sample = ((int)floatval) & 0xffff;

将浮点数转换为整数,并对其进行按位与运算,最终得到一个16位的整数。


这里取floatval/0xFFFF的余数,这不是 OP 想要的。 - Ponkadoodle
@Wallacoloo 当然不是。它“取”floatval/0x10000的余数。它与他使用的(short)完全相同,因此它可能正是他想要的。但是可能I2S更快。 - user207421
1
请原谅我在之前的评论中数学能力不佳,但是 OP 看起来需要截取行为,而不是位截断。例如,-40000.0 应该变成 -32768,而不是 25536(就像您的代码所做的那样)。 - Ponkadoodle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接