【问题标题】:How to write only regularly spaced items from a char buffer to disk in C++如何在 C++ 中仅将 char 缓冲区中的规则间隔项写入磁盘
【发布时间】:2010-07-20 15:11:13
【问题描述】:

如何在 C++ 中仅将 char 缓冲区中的每三个项目写入文件以快速归档?

我从相机中获得了三通道图像,但每个通道都包含相同的信息(图像是灰度的)。我只想将一个通道写入磁盘以节省空间并加快写入速度,因为这是实时数据收集系统的一部分。

C++ 的 ofstream::write 命令似乎只写入连续的二进制数据块,所以我当前的代码写入所有三个通道并且运行速度太慢:

char * data = getDataFromCamera();
int dataSize = imageWidth * imageHeight * imageChannels;
std::ofstream output;
output.open( fileName, std::ios::out | std::ios::binary );
output.write( data, dataSize );

我希望能够用如下调用替换最后一行:

int skipSize = imageChannels;
output.write( data, dataSize, skipSize );

其中 skipSize 会导致 write 仅将三分之一放入输出文件。但是,我找不到任何可以执行此操作的函数。

我很想听听有关将单个频道快速写入磁盘的任何想法。 谢谢。

【问题讨论】:

  • 通道数据是交错的还是连续的?也就是模式是123123123123还是111222333111222?
  • 我打赌交错。
  • 是的,它是交错的 - 123123123
  • 添加一个带有 codecvt facet 的 local,它只将每三个字节写入缓冲区。一旦您编写了构面,您就可以为任何流注入区域设置,并且它只会查看每三个字节。详情见下文。

标签: c++ file-io


【解决方案1】:

您可能必须将每隔三个元素复制到一个缓冲区中,然后将该缓冲区写入磁盘。

【讨论】:

  • +1 缓冲数据应该比一次写出一个字节给你一个明显的加速。你甚至可以继承 std::ofstream 并添加一个 write_pattern(char* ptr, int dataSize, int write_bytes, int skip_bytes) 成员函数来为你做这一切。
  • 看起来确实像是将必要的数据复制到自己的缓冲区中,然后将其写入磁盘是最好的选择。但是,正如下面 Tom Sirgedas 的回答所详述的那样,事实证明,通过意识到我真正需要的是每个像素的一个通道,但不一定每个像素的相同通道,我可以获得小幅加速。通过将输入缓冲区视为 uint32 并进行智能屏蔽,我可以做得更好,而不是天真地将每三个元素复制到一个新缓冲区。
  • 是的——汤姆的回答肯定得到了我的赞成;这是很不错的。实际上,看着它,我想我是唯一赞成它的人。哦,好吧...
【解决方案2】:

您可以在本地使用 codecvt facet 来过滤掉部分输出。
创建后,您可以为任何流注入适当的本地信息,并且它只会在输入中看到每三个字符。

#include <locale>
#include <fstream>
#include <iostream>

class Filter: public std::codecvt<char,char,mbstate_t>
{
    public:
   typedef std::codecvt<char,char,mbstate_t> MyType;
   typedef MyType::state_type          state_type;
   typedef MyType::result              result;

    // This indicates that we are converting the input.
    // Thus forcing a call to do_out()
    virtual bool do_always_noconv() const throw()   {return false;}

    // Reads   from -> from_end
    // Writes  to   -> to_end
    virtual result do_out(state_type &state,
             const char *from, const char *from_end, const char* &from_next,
             char       *to,   char       *to_limit, char*       &to_next) const
   {
       // Notice the increment of from
       for(;(from < from_end) && (to < to_limit);from += 3,to += 1)
       {
            (*to) = (*from);
       }
       from_next   = from;
       to_next     = to;

       return((to > to_limit)?partial:ok);
   }
};

一旦你有了这个方面,你所需要的就是知道如何使用它:

int main(int argc,char* argv[])
{
   // construct a custom filter locale and add it to a local.
   const std::locale filterLocale(std::cout.getloc(), new Filter());

   // Create a stream and imbue it with the locale
   std::ofstream   saveFile;
   saveFile.imbue(filterLocale);


   // Now the stream is imbued we can open it.
   // NB If you open the file stream first. 
   // Any attempt to imbue it with a local will silently fail.
   saveFile.open("Test");
   saveFile << "123123123123123123123123123123123123123123123123123123";

   std::vector<char>   data[1000];
   saveFile.write( &data[0], data.length() /* The filter implements the skipSize */ );
                                           // With a tinay amount of extra work
                                           // You can make filter take a filter size
                                           // parameter.

   return(0);
}

【讨论】:

    【解决方案3】:

    假设您的缓冲区是 24 位 RGB,并且您使用的是 32 位处理器(因此对 32 位实体的操作效率最高)。

    为了获得最快的速度,让我们一次处理一个 12 字节的块。在 12 个字节中,我们将有 4 个像素,如下所示:

    AAABBBCCCDDD
    

    这是 3 个 32 位值:

    AAAB
    BBCC
    CDDD
    

    我们希望将其转换为 ABCD(单个 32 位值)。

    我们可以通过对每个输入和 ORing 应用掩码来创建 ABCD。

    ABCD = A000 | 0BC0 | 000D
    

    在 C++ 中,使用 little-endian 处理器,我认为应该是:

    unsigned int turn12grayBytesInto4ColorBytes( unsigned int buf[3] )
    {
       return (buf[0]&0x000000FF) // mask seems reversed because of little-endianness
            | (buf[1]&0x00FFFF00)
            | (buf[2]&0xFF000000);
    }
    

    这可能是最快的另一种转换到另一个缓冲区然后转储到磁盘,而不是直接转到磁盘。

    【讨论】:

    • 这是一个不错的小技巧,确实可以显着加快速度!如果我必须在写入之前将数据复制到新的缓冲区,至少我可以以一种有趣且高效的方式来完成。
    【解决方案4】:

    标准库 afaik 中没有这样的功能。 Jerry Coffin 的解决方案效果最好。我写了一个简单的 sn-p 应该可以解决问题:

    const char * data = getDataFromCamera();
    const int channelNum = 0;
    const int channelSize = imageWidth * imageHeight;
    const int dataSize    = channelSize * imageChannels;
    char * singleChannelData = new char[channelSize];
    for(int i=0; i<channelSize ++i)
        singleChannelData[i] = data[i*imageChannels];
    try {
        std::ofstream output;
        output.open( fileName, std::ios::out | std::ios::binary );
        output.write( singleChannelData, channelSize );
    }
    catch(const std::ios_base::failure& output_error) {
        delete [] channelSize;
        throw;
    }
    delete [] singleChannelData;
    

    编辑: 我添加了 try..catch。当然,你也可以使用 std::vector 来获得更好的代码,但它可能会慢一点。

    【讨论】:

    • @Justin:您还需要除法 (singleChannelData[i/imageChannels]=data[i];),并且乘法通常比除法更快。而且 afaik 有用于 ++ 操作的特殊 cüu 指令,它可能也比 +=3 更快
    • 糟糕,我没想到。你是对的(是的,增加 1 可能比增加 3 更快)。
    【解决方案5】:

    首先,我要提一下,为了最大限度地提高写入速度,您应该写入多个扇区大小的缓冲区(例如 64KB 或 256KB)

    要回答您的问题,您必须将源数据中的每个第三个元素复制到另一个缓冲区中,然后将其写入流中。

    如果我没记错的话,Intel Performance Primitives 具有复制缓冲区、跳过一定数量元素的功能。使用 IPP 可能会比您自己的复制程序获得更快的结果。

    【讨论】:

      【解决方案6】:

      我很想说您应该将数据读入结构,然后重载插入运算符。

      ostream& operator<< (ostream& out, struct data * s) {
          out.write(s->first);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-02
        • 1970-01-01
        • 1970-01-01
        • 2015-03-16
        • 2011-09-14
        • 1970-01-01
        • 1970-01-01
        • 2016-08-12
        相关资源
        最近更新 更多