【问题标题】:How to avoid reloading big data on program start如何避免在程序启动时重新加载大数据
【发布时间】:2015-05-13 21:47:58
【问题描述】:

我的问题都是关于提示和技巧的。我目前正在做一个项目,我有一个非常大(~1Gb)的数据文件。首先,我需要提取数据。此提取需要 10 分钟。然后我进行计算。下一个计算取决于上一个。我们称它们为计算 1、计算 2 等等。假设我已经正确完成了提取部分,我目前面临两个问题:

  1. 每次我启动程序时,它至少工作 10 分钟。我无法避免,所以我必须计划调试。
  2. 下一次计算需要更多时间。

想到第一个问题,我假设,如果数据库比读取文件更快,某种数据库可能会有所帮助,我对此表示怀疑。

如果我将我的大程序拆分为多个小程序,则第二个问题可能会得到解决,每个小程序都可以:读取文件-执行内容-写入文件。所以下一阶段总是可以从以前的文件中读取文件,进行调试。但它为文件 I/O 引入了许多浪费的代码。

我认为这两个问题都可以通过某种策略来解决,例如:编写和测试提取模块,然后启动它并将所有数据提取到 RAM 中。比编写计算 1,然后启动它以某种方式直接从提取模块的 RAM 中获取数据。以此类推,每次下一次计算。所以我的问题是:

  1. 是否有最小化文件负载的提示和技巧?
  2. 有没有办法在程序之间共享 RAM 和对象?

顺便说一下,我在 Perl 上编写此任务是因为我需要它快,但我稍后会在 C++ 或 C# 上重写它,因此欢迎任何特定于语言或与语言无关的答案。

谢谢!

[编辑]

数据文件不会改变,它就像一个不可变的大知识源。而且它不完全是 1Gb,读取它不需要 10 分钟。我只是想说,那个文件很大,阅读它的时间很长。在我的机器上,1 Gb 读取+解析文件到正确的对象大约需要一分钟。这仍然很糟糕。

[/编辑]

【问题讨论】:

  • 考虑对文件进行 gzip 压缩以权衡 CPU 以减少 IO。另外,有多少列?数据是变化的还是稳定的?运行 memcached 的本地副本?或者将文件映射到内存?这完全取决于你到底在做什么。
  • 考虑管道架构。一个线程读取数据;当读取到足够的数据时,它会唤醒计算线程。计算获取数据并产生结果。它发送或通知结果的“写入”线程。写入线程输出结果。请记住,这三项活动是“同时”发生的。就像通过管道推东西一样。
  • 最快的解决办法是再买 16GB 的内存并运行 ramdisk
  • 尝试在提取阶段分析您的代码。它将帮助您确定需要这么长时间的原因。 Devel::NYTProf 是当今 perl 分析器的黄金标准。
  • 这听起来像是将您的数据导入数据库(如 MySQL)的不错选择,这将允许您将数据加载到内存中,正确设置索引,并且可以从多个进程/语言访问。

标签: c++ database perl optimization file-io


【解决方案1】:

在我当前的系统上,Perl 在 2 秒内将整个 1GB 文件复制到内存中。所以我相信你的问题不是读取文件而是解析它。

所以我能想到的直接解决方案是预解析它,例如,将您的数据转换为实际的代码源。我的意思是,您可以直接在脚本中准备数据并硬编码(当然使用另一个文件)。

但是,如果 阅读 是一个实际问题(我对此表示怀疑),您可以使用将数据存储在内存中的数据库 (example)。无论如何,它会更快,因为您的数据库在启动时读取数据一次,并且您不会像程序那样频繁地重新启动数据库。

【讨论】:

    【解决方案2】:

    解决这类问题的思路可以如下:

    参加 3 个项目:

    • 读者
    • 分析仪
    • 作家

    并使用共享内存在它们之间交换数据。

    对于那个大文件,我猜你有大量的一种对象类型的数据,你可以将它们存储在共享内存的循环缓冲区中(我建议使用 boost::interprocess)。

    Reader 会不断地从输入文件中读取数据并将其存储在共享内存中。 同时,一旦读取到足够的数据进行计算,分析器将开始处理它并将结果存储到另一个循环缓冲区共享内存文件中。 一旦在第二个共享内存中有一些计算,Writer 将读取它们并将它们存储到最终输出文件中。

    您需要确保所有进程都正确同步,以便它们同时完成工作并且您不会丢失数据(在处理或保存到最终文件之前数据不会被覆盖)。

    【讨论】:

      【解决方案3】:

      我喜欢 doqtor 给出的答案,但是为了防止数据被覆盖,一个很好的帮助类来启用和禁用线程中的代码的关键部分就可以了。

      // Note: I think sealed may be specific to Visual Studio Compiler.
      //       CRITICAL_SECTION is defined in Windows.h - If on another OS, 
      //       look for similar structure.
      class BlockThread sealed {
      private:
          CRITICAL_SECTION* m_pCriticalSection;
      
      public:
          explicit BlockThread( CRITICAL_SECTION& criticalSection );
          ~BlockThread();
      
      private:
          BlockThread( const BlockThread& c );
          BlockThread& operator=( const BlockThread& c ); // Not Implement
      };
      
      BlockThread::BlockThread( CRITICAL_SECTION& criticalSection ) {
          m_pCriticalSection = &criticalSection;
      }
      
      BlockThread::~BlockThread() {
          LeaveCriticalSection( m_pCriticalSection
      }
      

      如果您在使用共享内存的临界区范围内,这样的类将允许您阻塞特定线程 并且另一个线程当前可以访问它。这将导致这个线程 在当前线程完成工作之前要锁定的代码 类超出范围。

      在另一个类中使用这个类是相当简单的:在你的类中 想要在它的 .cpp 文件中阻塞一个线程,你需要创建一个这种类型的静态变量并调用 API 的函数来初始化它。然后 你可以使用 BlockThread 类来锁定这个线程。

      SomeClass.cpp

      #include "SomeClass.h"
      #include "BlockThread.h" 
      
      static CRITICAL_SECTION s_criticalSection;
      
      SomeClass::SomeClass {
          // Do This First
          InitializeCriticalSection( &s_criticalSection );
      
          // Class Stuff Here      
      }
      
      SomeClass::~SomeClass() {
          // Class Stuff Here
      
          // Do This Last
          DeleteCriticalSection( &s_criticalSection );
      }
      
      // To Use The BlockThread
      SomeClass::anyFunction() {
          // When Your Condition Is Met & You Know This Is Critical
          // Call This Before The Critical Computation Code.
          BlockThread blockThread( s_criticalSection );
      } 
      

      就是这样,一旦这个对象超出范围,静态成员 在对象析构函数中被清理,当这个对象出去时 BlockThread 类和它的 Destructor 在那里清理它的范围也是如此。 现在可以使用这个共享内存了。如果您要遍历容器以添加、插入或查找和访问元素,而此数据是共享类型时,您通常会希望使用此类。

      对于在同一数据集上运行在内存中的 3 个不同线程,一个好的概念是拥有 3 或 4 个缓冲区,每个缓冲区大小约为 4MB,并让它们以轮换顺序工作。 Buff1 获取数据,然后 Buff2 获取数据,而 Buff2 正在获取数据 Buff 1 正在解析数据或将其传递给存储以进行计算,然后 Buff1 等到 Buff3 或 4 完成,等待你有多少缓冲区。然后这个过程再次开始。这与在读取声音文件以进行音频流或将成批的三角形发送到图形卡时与声音缓冲区一起使用的原理相同。换句话说,它是一个批处理类型的过程。

      【讨论】:

        猜你喜欢
        • 2017-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-12
        • 2021-07-18
        • 2018-04-12
        • 2014-01-14
        相关资源
        最近更新 更多