10

文字列を何百万回も DateTime に解析します。

public static CultureInfo ci = CultureInfo.InvariantCulture;
while (!reader.EndOfStream)
{      
      line = reader.ReadLine();
      string[] fields = line.Split(' ');
      DateTime newDT = DateTime.ParseExact(fields[0], "yyyyMMddTHHmmssfff", ci);
}

私のプロファイラーは、ParseExact が時間の大きな部分を占めていることを強調しています。文字列をより高速な DateTime に解析できる他の方法/アプローチはありますか?

フォローアップ1:

1)これを試しましたが、速度は同じでした

bool OK = DateTime.TryParseExact(fields[0], "yyyyMMddTHHmmssfff", null, System.Globalization.DateTimeStyles.None,out DT);

2)

私は自分のパーサーをコーディングしようとしましたが、これも遅かったです:

public static DateTime fastParse(ref string s)
{
           return new DateTime(int.Parse(s.Substring(0,4)), int.Parse(s.Substring(4,2)),int.Parse(s.Substring(6,2)), int.Parse(s.Substring(9,2)),int.Parse(s.Substring(11,2)),int.Parse(s.Substring(13,2)),int.Parse(s.Substring(15, 3)));
}

フォローアップ2

値を保存するというMaster117の提案を試しました-再び高速ではありません-おそらく問題は構造ですか?

     public class fastParseData
        {
            int year;
            int mon;
            int day;
            int hour;
            int min; 
            string previousSlice = "";

            public DateTime fastParse(ref string s)
            {
                if (previousSlice != s.Substring(0, 12))
                {
                     year=int.Parse(s.Substring(0,4));
                     mon=int.Parse(s.Substring(4,2));
                     day=int.Parse(s.Substring(6,2));
                     hour= int.Parse(s.Substring(9,2));
                     min = int.Parse(s.Substring(11,2));
                     previousSlice = s.Substring(0, 12);
                }

                return new DateTime(year, mon, day, hour,min, int.Parse(s.Substring(13, 2)), int.Parse(s.Substring(15, 3)));
            }

        }

フォローアップ3

                public class fastParseData
                {
                    int year;
                    int mon;
                    int day;
                    int hour;
                    int min; 
                    string previousSlice = "";
                    DateTime previousDT;

                    public DateTime fastParse(ref string s)
                    {
                        if (previousSlice != s.Substring(0, 12))
                        {
                             year=int.Parse(s.Substring(0,4));
                             mon=int.Parse(s.Substring(4,2));
                             day=int.Parse(s.Substring(6,2));
                             hour= int.Parse(s.Substring(9,2));
                             min = int.Parse(s.Substring(11,2));
                             previousSlice = s.Substring(0, 12);
                            previousDT = new DateTime(year, mon, day, hour,min,0,0);
                        }
                        return previousDT.AddMilliseconds((int.Parse(s.Substring(13, 2))*1000)+int.Parse(s.Substring(15, 3)));
                    }

                }

フォローアップ4

私のプロファイラーから、核心は

int.Parse(s.Substring(13, 2))

解析ビットは部分文字列よりもコストがかかります。

私は試した

int.TryParse(s.Substring(13, 2),NumberStyles.None,ci, out secs)
Convert.ToInt32(s.Substring(13, 2));

繰り返しますが、速度に違いはありません。

int を解析するより高速な方法はありますか?

4

6 に答える 6

8

文字列を分割するというアイデアは正しい軌道に乗っていますが、部分文字列は遅いです。文字列を分割するときはいつでも、文字アクセサーを使用します。yyyyMMddTHHmmssfff 免責事項: T

public class DateParser1
{
    private static System.String DateFormat="yyMMddTHHmmssfff";

    public static System.DateTime GetDate(System.String SourceString, int Offset=0) // Offset eliminates need for substring
    {
        int Year=0;
        int Month=0;
        int Day=0;
        int Hour=0;
        int Minute=0;
        int Second=0;
        int HourOffset=0;
        int MS=0;
        if(SourceString.Length+Offset<DateFormat.Length) throw new System.Exception(System.String.Format("Date Too Short {0} For {0}",SourceString.Substring(Offset),DateFormat));
        for(int i=0;i<DateFormat.Length;i++)
        {
            System.Char c=SourceString[Offset+i];
            switch(DateFormat[i])
            {
                  case 'y':
                      Year=Year*10+(c-'0');
                      break;
                  case 'M':
                      Month=Month*10+(c-'0');
                      break;
                  case 'd':
                      Day=Day*10+(c-'0');
                      break;
                  case 'T':
                      if(c=='p'||c=='P')
                           HourOffset=12;
                      break;
                  case 'h':
                      Hour=Hour*10+(c-'0');
                      if(Hour==12) Hour=0;
                      break;
                  case 'H':

                      Hour=Hour*10+(c-'0');
                      HourOffset=0;
                      break;
                  case 'm':
                      Minute=Minute*10+(c-'0');
                      break;
                  case 's':
                      Second=Second*10+(c-'0');
                      break;
                  case 'f':
                      MS=MS*10+(c-'0');
                      break;
            }

        }
        if(Year>30) //Change For Your Business Rules
        {
               Year+=1900;
        }
        else
        {
               Year+=2000;
        }
        try
        {
            return new System.DateTime(Year,Month,Day,Hour+HourOffset,Minute,Second,MS);
        }
        catch(System.Exception)
        {
            throw new System.Exception(System.String.Format("Error In Date: {0}/{0}/{0} {0}:{0}:{0}.{0} - {0} {0}",Year,Month,Day,Hour+HourOffset,Minute,Second,MS,DateFormat,SourceString.SubString(Offset,DateFormat.Length)));
        }
    }
}
于 2013-06-04T14:42:37.697 に答える
2

This is the code used to benchmark James's code against the framework code (in reference to my comment there). I run this in Release mode, .Net 4.5.2, 32bit console

static void Main(string[] args)
{
    const string date = "2015-04-11T12:45:59";
    const string format = "yyyy-MM-ddTHH:mm:ss";

    var reference = FrameworkParse(date, format);
    var method1 = JamesBarrettParse(date, format);

    if (reference != method1)
    {
        throw new Exception(string.Format("reference date {0} does not match SO date {1}",reference.ToString("s"),method1.ToString("s")));
    }

    const int iterations = 1000000;
    var sw = new Stopwatch();

    //FRAMEWORK PARSE
    Console.Write("Starting framework parse for {0} iterations...", iterations);
    sw.Start();
    DateTime dt;
    for (var i = 0; i < iterations; i++)
    {
        dt = FrameworkParse(date, format);
        if (dt.Minute != 45)
        {
            Console.WriteLine("duh");
        }
    }
    sw.Stop();
    Console.WriteLine("DONE in {0} millis",sw.ElapsedMilliseconds.ToString("F2",CultureInfo.InvariantCulture));

    //James Barrett parse
    Console.Write("Starting JB parse for {0} iterations...", iterations);
    sw.Restart();
    for (var i = 0; i < iterations; i++)
    {
        dt = JamesBarrettParse(date, format);
        if (dt.Minute != 45)
        {
            Console.WriteLine("duh");
        }
    }
    sw.Stop();
    Console.WriteLine("DONE in {0} millis",sw.ElapsedMilliseconds.ToString("F2",CultureInfo.InvariantCulture));

    Console.Write("press any key to exit");
    Console.ReadKey();
}

private static DateTime FrameworkParse(string s, string format, CultureInfo info = null)
{
    var time = DateTime.ParseExact(s, format,
        info ?? CultureInfo.InvariantCulture,
        DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
    return time;
}

Output is

Starting framework parse for 1000000 iterations...DONE in 2058.00 millis
Starting JB parse for 1000000 iterations...DONE in 324.00 millis
press any key to exit

于 2015-04-11T15:50:20.417 に答える
2

独自の解析アルゴリズムを記述できます。最初に、文字列を配列/リスト/その他に分割し、Datetime コンストラクターを使用して Datetime を作成します。

DateTime newDT = DateTime(Int32, Int32, Int32, Int32, Int32, Int32, Int32);

年/月/日はそれほど速く変化しないため、BUFfer を実行できるため、文字列操作の数を減らすことができます。

http://msdn.microsoft.com/de-de/library/vstudio/system.datetime.aspx

簡単な方法は、string a = fields[0].slice(0,8) のように最初の 8 文字を保存することです (現時点では正しい操作はわかりません)。これらを解析して int を作成しますが、次の実行では、それらを再度スライスして、a = new a かどうかをテストします。そうであれば、再度解析する代わりに、前回の int を使用します。当然、a と整数を保存する必要があります。

したがって、問題は構築時間にあるように思われるので、addSecond などを使用して int が以前よりも高い/低いかどうかを確認して、経過時間を追加するか、構造を取得して値を新しい時間に設定することができます。 .

これを試して:

            public class fastParseData
            {
                int year;
                int mon;
                int day;
                int hour;
                int min; 
                string previousSlice = "";
                DateTime previousDT;

                public DateTime fastParse(ref string s)
                {
                    if (previousSlice != s.Substring(0, 12))
                    {
                         year=int.Parse(s.Substring(0,4));
                         mon=int.Parse(s.Substring(4,2));
                         day=int.Parse(s.Substring(6,2));
                         hour= int.Parse(s.Substring(9,2));
                         min = int.Parse(s.Substring(11,2));
                         previousSlice = s.Substring(0, 12);
                         previousDT = new DateTime(year, mon, day, hour,min,0,0);
                    }
                    return previousDT.ParseExact(year, mon, day, hour,min, int.Parse(s.Substring(13, 2)), int.Parse(s.Substring(15, 3));
                }

            }

そうすれば、あなただけがDTを作成し、新しい時間を設定するだけです

于 2013-03-29T11:20:56.897 に答える
1

これがより高速になるかどうかはわかりませんが、日付文字列を long に変換してから、次のように算術的に分割できます。

string dateStr = "20131108134701234"; //yyyyMMddHHmmssfff
long dateLong = long.Parse(dateStr);

int f = (int) (dateLong % 1000);
int s = (int) ((dateLong % 100000 - f) / 1000);
int mi = (int) ((dateLong % 10000000 - s - f) / 100000);
int h = (int) ((dateLong % 1000000000 - mi - s - f) / 10000000);
int d = (int) ((dateLong % 100000000000 - h - mi - s - f) / 1000000000);
int mo = (int) ((dateLong % 10000000000000 - d - h - mi - s - f) / 100000000000);
int y = (int) ((dateLong % 100000000000000000 - mo - d - h - mi - s - f) / 10000000000000);

DateTime dateDT = new DateTime(y, mo, d, h, mi, s, f);

(ナイーブで最適化されていない実装)

于 2013-11-08T14:07:07.647 に答える
0

これは、並列化を使用するのに適した場所かもしれません。Parallel.ForEach は 1 つの良い使用方法ですが、並列化を行う他の方法をテストすることもできます。

private static IEnumerable<string> GetLines(TextReader reader)
{
    while (!reader.EndOfStream)
    {
         yield return reader.ReadLine();
    }
}

private static CultureInfo ci = CultureInfo.InvariantCulture;

public static ConcurrentBag ProcessData(TextReader reader)
{
    ConcurrentBag <DateTime> results = new ConcurrentBag <DateTime>();
    char[] seperators = {' '};

    Parallel.ForEach(GetLines(reader), line =>
    {
        //We only need the first field so limit the split to 2
        string[] fields = line.Split(seperators, 2);
        results.Enqueue(DateTime.ParseExact(fields[0], "yyyyMMddTHHmmssfff", ci));
    });

    return results
}

これの欠点は、順序が緩いことです。それが重要な場合は、 IEnumerable からインデックスを渡すバージョン Parallel.ForEach があります。行番号を日付とともに保存し (おそらく aConcurrentDictionary<long,DateTime>または aTuple<long,DateTime>をバッグに保存)、後でデータをソートする必要があります。

于 2013-03-29T15:00:56.223 に答える