Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/69.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ ';memcpy'-类似于支持单个位偏移的函数?_C++_C_Optimization_Bit Manipulation_Memcpy - Fatal编程技术网

C++ ';memcpy'-类似于支持单个位偏移的函数?

C++ ';memcpy'-类似于支持单个位偏移的函数?,c++,c,optimization,bit-manipulation,memcpy,C++,C,Optimization,Bit Manipulation,Memcpy,我正在考虑解决这个问题,但这看起来是一项相当艰巨的任务。如果我自己拿这本书,我可能会用几种不同的方式来写,并选择最好的,所以我想我会问这个问题,看看是否有一个好的图书馆已经解决了这个问题,或者是否有人有想法/建议 void OffsetMemCpy(u8* pDest, u8* pSrc, u8 srcBitOffset, size size) { // Or something along these lines. srcBitOffset is 0-7, so the pSrc bu

我正在考虑解决这个问题,但这看起来是一项相当艰巨的任务。如果我自己拿这本书,我可能会用几种不同的方式来写,并选择最好的,所以我想我会问这个问题,看看是否有一个好的图书馆已经解决了这个问题,或者是否有人有想法/建议

void OffsetMemCpy(u8* pDest, u8* pSrc, u8 srcBitOffset, size size)
{
    // Or something along these lines. srcBitOffset is 0-7, so the pSrc buffer 
    // needs to be up to one byte longer than it would need to be in memcpy.
    // Maybe explicitly providing the end of the buffer is best.
    // Also note that pSrc has NO alignment assumptions at all.
}
我的应用程序是时间关键型的,所以我希望以最小的开销解决这个问题。这是困难/复杂性的根源。在我的例子中,这些块可能非常小,可能是4-12字节,因此大规模的memcpy内容(例如预取)并不那么重要。最好的结果是,对于恒定的“大小”输入,在4到12之间,对于随机未对齐的src缓冲区,能够以最快的速度工作

  • 内存应尽可能以字大小的块移动
  • 对齐这些字大小的块很重要。pSrc未对齐,因此我们可能需要从前端读取几个字节,直到对齐为止
有人有或知道类似的事情吗?还是有人想尝试写这篇文章,让它尽可能干净高效


编辑:人们似乎把这个“接近”选为“太宽”。一些狭义的细节可能是AMD64是首选的体系结构,因此让我们假设。这意味着little endian等。实现有望很好地符合答案的大小,因此我认为这不会太广泛。我要求的答案是一次只实现一个,即使有几种方法。

我将从一个简单的实现开始,例如:

inline void OffsetMemCpy(uint8_t* pDest, const uint8_t* pSrc, const uint8_t srcBitOffset, const size_t size)
{
    if (srcBitOffset == 0)
    {
        for (size_t i = 0; i < size; ++i)
        {
            pDest[i] = pSrc[i];
        }
    }
    else if (size > 0)
    {
        uint8_t v0 = pSrc[0];
        for (size_t i = 0; i < size; ++i)
        {
            uint8_t v1 = pSrc[i + 1];
            pDest[i] = (v0 << srcBitOffset) | (v1 >> (CHAR_BIT - srcBitOffset));
            v0 = v1;            
        }
    }
}
inline void OffsetMemCpy(uint8\u t*pDest、const uint8\u t*pSrc、const uint8\u t srcBitOffset、const size\u t size)
{
if(srcBitOffset==0)
{
对于(大小i=0;i0)
{
uint8_t v0=pSrc[0];
对于(大小i=0;i(字符位-srcBitOffset));
v0=v1;
}
}
}
(警告:未测试的代码!)

一旦这项功能发挥作用,然后在应用程序中对其进行分析-您可能会发现它的速度足以满足您的需求,从而避免过早优化的陷阱。如果没有,那么您有一个有用的基线参考实施,用于进一步的优化工作

请注意,对于小拷贝,对齐和字大小拷贝等的测试开销可能远远超过任何好处,因此,像上面这样简单的逐字节循环可能接近最佳


还要注意的是,优化很可能取决于体系结构-在一个CPU上带来好处的微优化可能会对另一个CPU产生相反的效果。

我认为简单的逐字节解决方案(参见@PaulR的答案)是小型块的最佳方法,除非您能够满足以下附加约束:

  • 输入缓冲区分配了一些填充,即在最后一个字节没有崩溃后访问一些字节
  • 输出缓冲区也分配了一些填充,如果覆盖了所需结果位置后的几个字节,这并不重要。如果这真的很重要,那么您需要做更多的工作来保存结束字节之后的内容
  • 与memcpy一样,所涉及的输入和输出范围不会重叠(包括结尾后的几个填充字节)
  • 如果可以,则可以增加算法的粒度。将@PaulR的答案更改为使用
    uint64\u t
    单词而不是
    uint8\u t
    字节非常容易。因此,它的工作速度会更快

    我们可以使用SSE进一步增加单词大小。由于在SSE中无法将整个寄存器按位移位,我们必须对64位整数进行两次移位,然后将结果粘合在一起。胶接由SSSE3的
    \u mm\u shuffle\u epi8
    完成,它允许以任意方式对XMM寄存器中的字节进行混洗。对于移位,我们使用
    \u mm\u srl\u epi64
    ,因为这是将64位整数按非立即位数移位的唯一方法。我在指针参数中添加了C(作为宏)中的
    restrict
    关键字,因为如果它们有别名,算法无论如何都不会工作

    代码如下:

    void OffsetMemCpy_stgatilov(uint8_t *RESTRICT pDest, const uint8_t *RESTRICT pSrc, const uint8_t srcBitOffset, const size_t size) {
        __m128i bits = (sizeof(size_t) == 8 ? _mm_cvtsi64_si128(srcBitOffset) : _mm_cvtsi32_si128(srcBitOffset));
        const uint8_t *pEnd = pSrc + size;
        while (pSrc < pEnd) {
            __m128i input = _mm_loadu_si128((__m128i*)pSrc);
            __m128i reg = _mm_shuffle_epi8(input, _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10, 11, 12, 13, 14));
            __m128i shifted = _mm_srl_epi64(reg, bits);
            __m128i comp = _mm_shuffle_epi8(shifted, _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, -1, -1));
            _mm_storeu_si128((__m128i*)pDest, comp);
            pSrc += 14;  pDest += 14;
        }
    }
    
    (billions of calls per second)
    size = 4:
      0.132  (Paul R)
      0.248  (Paul R x64)
      0.45  (stgatilov)
    size = 8:
      0.0782  (Paul R)
      0.249  (Paul R x64)
      0.45  (stgatilov)
    size = 12:
      0.0559  (Paul R)
      0.191  (Paul R x64)
      0.453  (stgatilov)
    
    说整个函数在Ivy桥上需要4.5个周期的吞吐量和13个周期的延迟,因为循环只执行一次,缓存/分支/解码没有问题。然而,在基准测试中,一次这样的调用平均花费7.5个周期

    以下是Ivy Bridge 3.4 Ghz吞吐量基准测试的简要结果(请参阅代码中的更多结果):

    然而,请注意,在现实世界中,性能可能与基准测试结果截然不同


    具有基准测试和更详细结果的完整代码是。

    首先编写一个干净简单的版本(应该很简单),然后在应用程序中对其进行分析-您可能会发现它足够快,足以满足您的需要,从而避免过早优化的陷阱。如果没有,那么您就有了一个有用的基线参考实现,可以进行进一步的工作。是的,这就是我现在正在做的。我不敢相信这个问题还没有解决。不过,这是针对国际象棋引擎中的哈希表,我要替换的整个代码仅在几行代码中就占用了60%的CPU。我以前从未在国际象棋程序哈希表中见过未对齐的访问。它们通常是非常仔细的缓存线对齐。请参见:选择C和C++中的一个。同意解决方案,并且我注意到,由于字节是连续的,所以可以通过使用32位或64位整数块(直到最后块)来获得进一步的加速。(验证大或小端点不重要)…如果大小不变,则可以自定义您的算法,而无需太多分支逻辑(如果有的话)。(OP说,“最好的结果是最快的板凳
    (billions of calls per second)
    size = 4:
      0.132  (Paul R)
      0.248  (Paul R x64)
      0.45  (stgatilov)
    size = 8:
      0.0782  (Paul R)
      0.249  (Paul R x64)
      0.45  (stgatilov)
    size = 12:
      0.0559  (Paul R)
      0.191  (Paul R x64)
      0.453  (stgatilov)