zoukankan      html  css  js  c++  java
  • 亿级数据时,内存性能低于IO性能

    最近因项目需要,需要生成有0到99999999共1亿的不重复数,于是想着直接将这些数据生成为一个文件就可以了,代码如。

    private void generate(string savePath)
            {
                int begin = 1;
                int end = 99999999;
                Encoding encoding = Encoding.UTF8;
                FileStream destStream = new FileStream(savePath,FileMode.Create,FileAccess.ReadWrite,FileShare.None);
                for (int i = begin; i <= end; i++)
                {               
                    string code = string.Format("{0:00000000}", i);
                    byte[] codeBytes = encoding.GetBytes(code);
                    destStream.Write(codeBytes, 0, 8);
                }
                destStream.Close();
            }


    但是项目要求数据必须是随机的,所以必须要将这些数据进行随机,那么直接按顺序(比如1到99999999)生成1亿的数据显然无法满足要求,于是就想着是不是可以在生成数据时参与随机算法。代码如下

    private void generate(string savePath)
            {
                int begin = 1;
                int end = 99999999;
                Encoding encoding = Encoding.UTF8;
                FileStream destStream = new FileStream(savePath,FileMode.Create,FileAccess.ReadWrite,FileShare.None);
                List<byte[]> codeList = new List<byte[]>();
                for (int i = begin; i <= end; i++)
                {               
                    string code = string.Format("{0:00000000}", i);
                    byte[] codeBytes = encoding.GetBytes(code);
                    codeList.Add(codeBytes);                
                }
                while (codeList.Count>0)
                {
                    int index = generateRandomInt(0, codeList.Count);
                    byte[] tempCodeByte = codeList[index];
                    codeList.RemoveAt(index);
                    destStream.Write(tempCodeByte, 0, 8);
                }
                destStream.Close();
            }
     private int generateRandomInt(int min, int max)
            {
                Random random = new Random(Guid.NewGuid().GetHashCode());
                int randomInt = random.Next(min, max);
                return randomInt;
            }

    其中随机数算法,因为Random随机时,所用的种子一样时,产生的随机序列是一样的,所以采用GUID全球唯一标识符的哈稀码来作为种子,以使随机种子不同,从而产生不同的随机数。

    但是这么做了之后,会发现程序虽然可以跑起来,但是由于数据量太大,所以采用List来存储数据会消耗大量的内存,最终电脑卡死崩溃。我的电脑配置是Win764位、4G内存、奔腾主频2.7GHZ的,但是处理起来后,内存耗达到了98%,CPU更是居高不下,最终无奈的只得重启电脑。

    所以上面的代码理论上是可行的,但是当处理1亿级的数据时,就出现了瓶颈。或许会说是随机算法不太合理,不应当用List来存储。是的,我也是这么觉得,但一时又想不到好的方法。后来转念一想,现在个人电脑的主流内存一般都是2G/4G/8G,而硬盘随随便都是几百G,甚至T级,如果可以把数据放到硬盘上去随机,那是不是就可以达到目的了?

    这么一想后,思路就转变成怎么将数据随机到硬盘上去了。要把数据随机到硬盘上,那只能是写文件。那又要怎么去实现随机呢?如果预先产生了10000个文件,然后再将生成的数据随机写到文件中,最后将这些文件再合并起来,那不就实现了随机吗?代码实现如下

     private void generate(string savePath)
            {
                int blockCount = 20000;
                FileStream[] streams = new FileStream[blockCount];
                string[] paths = new string[blockCount];
                string tempDir = Application.StartupPath + @"Temp";
                Directory.CreateDirectory(tempDir);
                for (int index = 1; index <= blockCount; index++)
                {
                    string tempPath = tempDir + @"" + index.ToString() + ".dat";
                    paths[index - 1] = tempPath;
                    streams[index - 1] = new FileStream(tempPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
                }
                int begin = 1;
                int end = 99999999;
                Encoding encoding = Encoding.UTF8;        
                List<byte[]> codeList = new List<byte[]>();
                for (int i = begin; i <= end; i++)
                {               
                    int streamIndex = generateRandomInt(0, blockCount);
                    string code = string.Format("{0:00000000}", i);
                    byte[] codeBytes = encoding.GetBytes(code);
                    streams[streamIndex].Write(codeBytes, 0, 8);              
                }
                mergeMultiFileStream(savePath, streams, 8);            
            }
    
            private void mergeMultiFileStream(string savePath,
                                         FileStream[] streams,
                                         int dataBlockLength = 8)
            {
                FileStream destStream = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
    
    
                for (int index = 0; index < streams.Length; index++)
                {
                    int streamIndex = index;               
                    FileStream stream = streams[streamIndex];
                    stream.Position = 0;
                    byte[] tempCodeBytes = new byte[stream.Length];
                    stream.Read(tempCodeBytes, 0, tempCodeBytes.Length);               
                    destStream.Write(tempCodeBytes, 0, tempCodeBytes.Length);
                    stream.Close();            
                }
                destStream.Close();
            }

    再运行程序后,内存大概是60%左右,而CPU大概是50%左右,大约17分钟后,生成了最终数据,但是用UE打开后一看,发现数据自前后后,整体还是呈现自小而大块状分布。这是怎么回事呢?仔细一想,恍然大悟,在程序生成数据时,是按顺序生成的,虽然将这些数据写入文件时,数据是随机写入的,但是整体上是小的数据写在了前面,大的数据写在了后面,最终合并时是将文件一个一个合并的,所以数据整体上还是自小而大的,只是会以一块一块的形式呈现。那有没有什么办法可以改进呢?

    0到99999999个数据,每个数据必须要8位,以8个字节存储,理论上是有786M左右,分成了10000份后,就有78.8K左右,这样的数据如果放到内存里面,然后再随机,显然内存是可以承受的,于是在合并文件时,又将每个文件读到的数据增加了一次随机处理。将mergeMultiFileStream函数的代码修改如下:

    private void mergeMultiFileStream(string savePath,
                                         FileStream[] streams,
                                         int dataBlockLength = 8)
            {
                FileStream destStream = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
    
    
                for (int index = 0; index < streams.Length; index++)
                {
                    int streamIndex = index;               
                    FileStream stream = streams[streamIndex];
                    stream.Position = 0;
                    byte[] tempCodeBytes = new byte[stream.Length];
                    stream.Read(tempCodeBytes, 0, tempCodeBytes.Length);
                    tempCodeBytes = randomData(tempCodeBytes, dataBlockLength);
                    destStream.Write(tempCodeBytes, 0, tempCodeBytes.Length);
                    stream.Close();            
                }
                destStream.Close();
            }
    
    private byte[] randomData(byte[] sourceData, int randomDataBlockLength)
            {
                List<byte[]> tempData = new List<byte[]>();
                for (int index = 0; index < sourceData.Length; index += randomDataBlockLength)
                {
                    int dataLength = randomDataBlockLength;
                    if (sourceData.Length - index < randomDataBlockLength)
                    {
                        dataLength = sourceData.Length - index;
                    }
                    byte[] tempDataBlock = new byte[dataLength];
                    Array.Copy(sourceData, index, tempDataBlock, 0, dataLength);
                    tempData.Add(tempDataBlock);
                }
                byte[] newData = new byte[sourceData.Length];
                int newDataCopyBeginIndex = 0;
                while (tempData.Count > 0)
                {
                    int index = generateRandomInt(0, tempData.Count);
                    byte[] tempDataBlock = tempData[index];
                    Array.Copy(tempDataBlock, 0, newData, newDataCopyBeginIndex, tempDataBlock.Length);
                    newDataCopyBeginIndex += tempDataBlock.Length;
                    tempData.RemoveAt(index);
                }
                tempData.Clear();
                return newData;
            }

    这里要注意的是,为了确保randomData函数中所用的List能释放,增加了tempData.Clear()的处理,以清空List,从而避免因垃圾回收不够及时,而导致内存占用过多的情况。

    这样一样,程序再次跑了起来,得到的数据已经随机了。如果还要进一步随机,可以将生成后的文件,再次读取分成多个文件(如10000),再随机处理合并。

    达到了目的之后,就想着怎么去优化,能不能在更短的时间里去处理完数据,于是想到了多线程。但是当我将多线程加进去处理后,发现并不能达到太理想的效果,因为CPU和内存的占用又一次居高不下,电脑又一次卡死。

    这是怎么回事?

    分析了一番之后,得出这样的结论,一般情况下,若是采用多线程处理,确实可以加快速度。但是由于这里的数据量太大,需要开多一点的线程来处理数据,才能提升速度,但是当分成多个线程处理时,虽然单位时间内多个线程可以并行处理数据,但这样一样,每个线程都会消耗内存,都要CPU处理,所以线程一多,内存消耗就会变大,CPU的占用也就会变高。

    可能是我当时开了太多线程导致了这样的结果,或许开少一占的线程,比如5个或者10个,但这样一来,又需要协调好文件数量的分配,让每个线程处理若干个文件,而为了协调好这些,需要有自增操作(文件流数组索引是逐个递增的),而在多线程中的自增操作,必须采用原子性的增加(Interlock.Increacement),否则会出现自增时的竞争,从而导到意外的结果。比如A线程要用某个流时,因流数据索引的错位,导致B线程也使用到了同一个流,而B线程又快于A线程处理完数据,结果 流被关闭,A线程无法操作。即便随开了流的关闭,但A线程和B线程引用了同样的流,这样的数据就会出现重复,无法满足项目要求。

    所以最终,为了确保数据的准确性,没有采用多线程来处理。

    从这里也看出另一点,虽然使用内存来处理数据比IO处理数据时更快,但是由于内存大小的局限,当数据量达到一定量级时,内存就会捉襟见肘,这时不可避免的要使用磁盘来转存数据,以分担内存的消耗。所以在亿级数据时,如果全部采用内存来处理,其性能并不会高于IO操作的性能。

    转载请注明出处http://blog.csdn.net/xxdddail/article/details/12645957


  • 相关阅读:
    The Mac Application Environment 不及格的程序员
    Xcode Plugin: Change Code In Running App Without Restart 不及格的程序员
    The property delegate of CALayer cause Crash. 不及格的程序员
    nil localizedTitle in SKProduct 不及格的程序员
    InApp Purchase 不及格的程序员
    Safari Web Content Guide 不及格的程序员
    在Mac OS X Lion 安装 XCode 3.2 不及格的程序员
    illustrate ARC with graphs 不及格的程序员
    Viewing iPhoneOptimized PNGs 不及格的程序员
    What is the dSYM? 不及格的程序员
  • 原文地址:https://www.cnblogs.com/pangblog/p/3366078.html
Copyright © 2011-2022 走看看