2

ハードドライブのテキスト ファイルから IMDB 映画のリストを読んでいます (元はftp://ftp.fu-berlin.de/pub/misc/movies/database/movies.list.gzの IMDB サイトから入手できます)。

私のマシン (基本情報: Win7 x64bit、16GB RAM、500 GB SATA Hardisk 7200 RPM) では、以下のコードを使用してこのファイルを 1 行ずつ読み取るのに約 5 分かかります。

2 つの質問があります。

  1. 読み取り時間を改善するためにコードを最適化する方法はありますか?

  2. 一度に1行ずつ読み取る限り、データを上から下/下から上、またはその順序で読み取ることを気にしないため、データアクセスはシーケンシャルである必要はありません。読書時間を改善するために複数の方向に読む方法はあるのでしょうか?

アプリケーションは Windows コンソール アプリケーションです。

更新: 多くの回答が、コンソールへの書き込みにかなりの時間がかかることを正しく指摘しました。Windows コンソールにデータを表示することは現在では望ましいことですが、必須ではないことを考慮してください。

//コードブロック

string file = @"D:\movies.list";

FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None, 8, FileOptions.None);

using (StreamReader sr = new StreamReader(fs))
{
  while (sr.Peek() >= 0)
  {
    Console.WriteLine(sr.ReadLine());
  }
}
4

5 に答える 5

0

私はac#開発者ではありませんが、ファイルを使用してデータベースに一括挿入するのはどうですか(これは1回限りです)。次に、データを再利用してエクスポートすることもできます。

于 2012-07-07T05:05:25.500 に答える
0

まず、リストをコンソールに出力することに関心がない場合は、質問を編集してください。

次に、提案されたさまざまな方法の速度をテストするためのタイミング プログラムを作成しました。

class Program
{
    private static readonly string file = @"movies.list";

    private static readonly int testStart = 1;
    private static readonly int numOfTests = 2;
    private static readonly int MinTimingVal = 1000;

    private static string[] testNames = new string[] {            
        "Naive",
        "OneCallToWrite",
        "SomeCallsToWrite",
        "InParallel",
        "InParallelBlcoks",
        "IceManMinds",
        "TestTiming"
        };

    private static double[] avgSecs = new double[numOfTests];

    private static int[] testIterations = new int[numOfTests];

    public static void Main(string[] args)
    {
        Console.WriteLine("Starting tests...");
        Debug.WriteLine("Starting tests...");

        Console.WriteLine("");
        Debug.WriteLine("");

        //*****************************
        //The console is the bottle-neck, so we can
        //speed-up redrawing it by only showing 1 line at a time.
        Console.WindowHeight = 1;
        Console.WindowWidth = 50;

        Console.BufferHeight = 100;
        Console.BufferWidth = 50;
        //******************************

        Action[] actionArray = new Action[numOfTests];

        actionArray[0] = naive;
        actionArray[1] = oneCallToWrite;
        actionArray[2] = someCallsToWrite;
        actionArray[3] = inParallel;
        actionArray[4] = inParallelBlocks;
        actionArray[5] = iceManMinds;
        actionArray[6] = testTiming;


        for (int i = testStart; i < actionArray.Length; i++)
        {
            Action a = actionArray[i];
            DoTiming(a, i);
        }

        printResults();

        Console.WriteLine("");
        Debug.WriteLine("");

        Console.WriteLine("Tests complete.");
        Debug.WriteLine("Tests complete.");

        Console.WriteLine("Press Enter to Close Console...");
        Debug.WriteLine("Press Enter to Close Console...");

        Console.ReadLine();
    }

    private static void DoTiming(Action a, int num)
    {
        a.Invoke();

        Stopwatch watch = new Stopwatch();
        Stopwatch loopWatch = new Stopwatch();

        bool shouldRetry = false;

        int numOfIterations = 2;

        do
        {
            watch.Start();

            for (int i = 0; i < numOfIterations; i++)
            {
                a.Invoke();
            }

            watch.Stop();

            shouldRetry = false;

            if (watch.ElapsedMilliseconds < MinTimingVal) //if the time was less than the minimum, increase load and re-time.
            {
                shouldRetry = true;
                numOfIterations *= 2;
                watch.Reset();
            }

        } while (shouldRetry);

        long totalTime = watch.ElapsedMilliseconds;

        double avgTime = ((double)totalTime) / (double)numOfIterations;

        avgSecs[num] = avgTime / 1000.00;
        testIterations[num] = numOfIterations;
    }

    private static void printResults()
    {
        Console.WriteLine("");
        Debug.WriteLine("");

        for (int i = testStart; i < numOfTests; i++)
        {
            TimeSpan t = TimeSpan.FromSeconds(avgSecs[i]);

            Console.WriteLine("ElapsedTime: {0:N4}, " + "test: " + testNames[i], t.ToString() );
            Debug.WriteLine("ElapsedTime: {0:N4}, " + "test: " + testNames[i], t.ToString() );
        }
    }

    public static void naive()
    {
        FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None, 8, FileOptions.None);

        using (StreamReader sr = new StreamReader(fs))
        {
            while (sr.Peek() >= 0)
            {
                 Console.WriteLine( sr.ReadLine() );

            }
        }
    }

    public static void oneCallToWrite()
    {
        FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None, 8, FileOptions.None);

        using (StreamReader sr = new StreamReader(fs))
        {
            StringBuilder sb = new StringBuilder();

            while (sr.Peek() >= 0)
            {
                string s = sr.ReadLine();

                sb.Append("\n" + s);
            }

            Console.Write(sb);
        }
    }

    public static void someCallsToWrite()
    {
        FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None, 8, FileOptions.None);

        using (StreamReader sr = new StreamReader(fs))
        {
            StringBuilder sb = new StringBuilder();
            int count = 0;
            int mod = 10000;

            while (sr.Peek() >= 0)
            {
                count++;

                string s = sr.ReadLine();

                sb.Append("\n" + s);

                if (count % mod == 0)
                {
                    Console.Write(sb);
                    sb = new StringBuilder();
                }
            }

            Console.Write( sb );
        }
    }

    public static void inParallel()
    {
        string[] wordsFromFile = File.ReadAllLines( file );

        int length = wordsFromFile.Length;

        Parallel.For( 0, length, i => {

            Console.WriteLine( wordsFromFile[i] );

        });

    }

    public static void inParallelBlocks()
    {
        string[] wordsFromFile = File.ReadAllLines(file);

        int length = wordsFromFile.Length;

        Parallel.For<StringBuilder>(0, length,
            () => { return new StringBuilder(); },
            (i, loopState, sb) =>
            {
                sb.Append("\n" + wordsFromFile[i]);
                return sb;
            },
            (x) => { Console.Write(x); }
        );

    }

    #region iceManMinds

    public static void iceManMinds()
    {
        string FileName = file;
        long ThreadReadBlockSize = 50000;
        int NumberOfThreads = 4;
        byte[] _inputString;


        var fi = new FileInfo(FileName);
        long totalBytesRead = 0;
        long fileLength = fi.Length;
        long readPosition = 0L;
        Console.WriteLine("Reading Lines From {0}", FileName);
        var threads = new Thread[NumberOfThreads];
        var instances = new ReadThread[NumberOfThreads];
        _inputString = new byte[fileLength];

        while (totalBytesRead < fileLength)
        {
            for (int i = 0; i < NumberOfThreads; i++)
            {
                var rt = new ReadThread { StartPosition = readPosition, BlockSize = ThreadReadBlockSize };
                instances[i] = rt;
                threads[i] = new Thread(rt.Read);
                threads[i].Start();
                readPosition += ThreadReadBlockSize;
            }
            for (int i = 0; i < NumberOfThreads; i++)
            {
                threads[i].Join();
            }
            for (int i = 0; i < NumberOfThreads; i++)
            {
                if (instances[i].BlockSize > 0)
                {
                    Array.Copy(instances[i].Output, 0L, _inputString, instances[i].StartPosition,
                               instances[i].BlockSize);
                    totalBytesRead += instances[i].BlockSize;
                }
            }
        }

        string finalString = Encoding.ASCII.GetString(_inputString);
        Console.WriteLine(finalString);//.Substring(104250000, 50000));
    }

    private class ReadThread
    {
        public long StartPosition { get; set; }
        public long BlockSize { get; set; }
        public byte[] Output { get; private set; }

        public void Read()
        {
            Output = new byte[BlockSize];
            var inStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            inStream.Seek(StartPosition, SeekOrigin.Begin);
            BlockSize = inStream.Read(Output, 0, (int)BlockSize);
            inStream.Close();
        }
    }

    #endregion

    public static void testTiming()
    {
        Thread.Sleep(500);
    }
}

これらの各テストでは、ファイルがコンソールに出力されます。

デフォルトのコンソール設定で実行した場合、各テストは 5:30 から 6:10 (Min:Sec) の間にかかりました。

Console のプロパティを考慮した後、Console.WindowHeight = 1、つまり一度に 1 行だけ表示することで (最新の 100 行を表示するために上下にスクロールできます)、高速化を達成しました。

現在、タスクはほとんどの方法で 2:40 (分:秒) 強で完了します。

コンピューターで試してみて、どのように機能するかを確認してください。

興味深いことに、さまざまな方法は基本的に同等であり、OP のコードが基本的に最速でした。

タイミング コードは、コードをウォームアップしてから 2 回実行し、所要時間を平均します。これを各メソッドに対して行います。

独自の方法と時間を試してみてください。

于 2012-07-11T23:15:08.230 に答える
0

この質問に対する答えは、データをどう扱うかによって異なります。本当にファイルを読み込んで内容をコンソール画面にダンプするだけの場合は、StringBuilder クラスを使用して、たとえば 1000 行の文字列を作成し、内容を画面にダンプしてリセットすることをお勧めします。次に、文字列をさらに 1000 行で読み取り、ダンプします。

ただし、大規模なプロジェクトの一部である何かを構築しようとしていて、.NET 4.0 を使用している場合は、MemoryMappedFile クラスを使用してファイルを読み取り、CreateViewAccessorを作成して、その一部だけで動作する「ウィンドウ」を作成できます。ファイル全体を読み取るのではなく、データを読み取ります。

別のオプションは、ファイルのさまざまな部分を一度に読み取り、最後にすべてをまとめるスレッドを作成することです。

このデータをどうするつもりなのか、もっと具体的に教えていただければ、もっとお手伝いできます。お役に立てれば!

編集:

このコードを試してみてください。スレッドを使用して、文字通り 3 秒でリスト全体を読み取ることができました。

using System;
using System.IO;
using System.Text;
using System.Threading;

namespace ConsoleApplication36
{
    class Program
    {
        private const string FileName = @"C:\Users\Public\movies.list";
        private const long ThreadReadBlockSize = 50000;
        private const int NumberOfThreads = 4;
        private static byte[] _inputString;

        static void Main(string[] args)
        {
            var fi = new FileInfo(FileName);
            long totalBytesRead = 0;
            long fileLength = fi.Length;
            long readPosition = 0L;
            Console.WriteLine("Reading Lines From {0}", FileName);
            var threads = new Thread[NumberOfThreads];
            var instances = new ReadThread[NumberOfThreads];
            _inputString = new byte[fileLength];

            while (totalBytesRead < fileLength)
            {
                for (int i = 0; i < NumberOfThreads; i++)
                {
                    var rt = new ReadThread { StartPosition = readPosition, BlockSize = ThreadReadBlockSize };
                    instances[i] = rt;
                    threads[i] = new Thread(rt.Read);
                    threads[i].Start();
                    readPosition += ThreadReadBlockSize;
                }
                for (int i = 0; i < NumberOfThreads; i++)
                {
                    threads[i].Join();
                }
                for (int i = 0; i < NumberOfThreads; i++)
                {
                    if (instances[i].BlockSize > 0)
                    {
                        Array.Copy(instances[i].Output, 0L, _inputString, instances[i].StartPosition,
                                   instances[i].BlockSize);
                        totalBytesRead += instances[i].BlockSize;
                    }
                }
            }

            string finalString = Encoding.ASCII.GetString(_inputString);
            Console.WriteLine(finalString.Substring(104250000, 50000));
        }

        private class ReadThread
        {
            public long StartPosition { get; set; }
            public long BlockSize { get; set; }
            public byte[] Output { get; private set; }

            public void Read()
            {
                Output = new byte[BlockSize];
                var inStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                inStream.Seek(StartPosition, SeekOrigin.Begin);
                BlockSize = inStream.Read(Output, 0, (int)BlockSize);
                inStream.Close();
            }
        }
    }
}

movie.list ファイルの場所に一致するように FileName を変更する必要があります。また、スレッドの総数を調整することもできます。私は 4 を使用しましたが、これは自由に増減できます。また、ブロック サイズを変更することもできます...これは、各スレッドが読み込むデータの量です。また、ASCII テキスト ファイルを想定しています。そうでない場合は、エンコーディング タイプを UTF8 またはファイルのエンコーディングに変更する必要があります。頑張ってください!

于 2012-07-07T05:46:58.460 に答える
0

これがより効率的かどうかはわかりませんが、別の方法はFile.ReadAllLinesを使用することです:

var movieFile = File.ReadAllLines(file);
foreach (var movie in movieFile)
    Console.WriteLine(movie);
于 2012-07-07T04:28:49.137 に答える
0

.net 4 では、遅延評価に File.ReadLines を使用できるため、大きなファイルを操作する際の RAM 使用量を削減できます。

ファイルに対して直接 linq 操作を実行できます。これと File.ReadLines を併用すると、読み込み時間が短縮されます。

理解を深めるために、LINQ を使用してテキスト ファイルを単語ごとに読み取るを確認してください。

比較もできますが、時間間隔を置いてください。

ただし、Web アプリを作成する場合は、アプリケーションの開始イベントでファイル全体を読み取り、それらをアプリケーション プールにキャッシュして、パフォーマンスを向上させることができます。

于 2012-07-07T04:38:42.337 に答える