Cache property lookups for the object parsing
This commit is contained in:
@@ -50,6 +50,7 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Parsers\LeafInputCsvParserTests.cs" />
|
<Compile Include="Parsers\LeafInputCsvParserTests.cs" />
|
||||||
<Compile Include="Remote\PiscalSshClientTests.cs" />
|
<Compile Include="Remote\PiscalSshClientTests.cs" />
|
||||||
|
<Compile Include="Utility\MemoizerTests.cs" />
|
||||||
<Compile Include="Utility\StringExtensionsTests.cs" />
|
<Compile Include="Utility\StringExtensionsTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using LeafWeb.Core.Entities;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using LeafWeb.Core.Entities;
|
||||||
using LeafWeb.Core.Parsers;
|
using LeafWeb.Core.Parsers;
|
||||||
using LeafWeb.Core.Utility;
|
using LeafWeb.Core.Utility;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -21,5 +23,28 @@ namespace LeafWeb.Core.Tests.Parsers
|
|||||||
|
|
||||||
Assert.That(leafGasComparisons.Length, Is.EqualTo(6));
|
Assert.That(leafGasComparisons.Length, Is.EqualTo(6));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//[Test, Explicit]
|
||||||
|
public void Parse_Timer()
|
||||||
|
{
|
||||||
|
var smallFileInfo = FileUtility.GetContentFile(ContentDirectory, "leafgascomparison.csv");
|
||||||
|
var largeFileInfo = FileUtility.GetContentFile(@"c:\temp\", "20160411095955C3_leafgascomparison.csv");
|
||||||
|
var timer = new Stopwatch();
|
||||||
|
|
||||||
|
timer.Start();
|
||||||
|
using (var parser = new LeafGasComparisonParser(smallFileInfo))
|
||||||
|
parser.Parse();
|
||||||
|
timer.Stop();
|
||||||
|
|
||||||
|
Console.WriteLine($"{timer.ElapsedMilliseconds}");
|
||||||
|
|
||||||
|
timer.Reset();
|
||||||
|
timer.Start();
|
||||||
|
using (var parser = new LeafGasComparisonParser(largeFileInfo))
|
||||||
|
parser.Parse();
|
||||||
|
timer.Stop();
|
||||||
|
|
||||||
|
Console.WriteLine($"{timer.ElapsedMilliseconds}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using LeafWeb.Core.Utility;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace LeafWeb.Core.Tests.Utility
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class MemoizerTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void ThreadsafeMemoize_Test()
|
||||||
|
{
|
||||||
|
Func<string, int, string> func = (a1, a2) => a1 + a2.ToString();
|
||||||
|
|
||||||
|
var funcMem = Memoizer.ThreadsafeMemoize(func);
|
||||||
|
|
||||||
|
var result = funcMem("hi", 1);
|
||||||
|
Assert.That(result, Is.EqualTo("hi1"));
|
||||||
|
|
||||||
|
var resultAgain = funcMem("hi", 1);
|
||||||
|
Assert.That(resultAgain, Is.EqualTo("hi1"));
|
||||||
|
|
||||||
|
var differentResult = funcMem("this", 2);
|
||||||
|
Assert.That(differentResult, Is.EqualTo("this2"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -104,10 +104,13 @@
|
|||||||
<Compile Include="Parsers\CsvParserBase.cs" />
|
<Compile Include="Parsers\CsvParserBase.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Utility\FileUtility.cs" />
|
<Compile Include="Utility\FileUtility.cs" />
|
||||||
|
<Compile Include="Utility\Memoizer.cs" />
|
||||||
<Compile Include="Utility\ParseInfoAttribute.cs" />
|
<Compile Include="Utility\ParseInfoAttribute.cs" />
|
||||||
<Compile Include="Parsers\LeafInputCsvParser.cs" />
|
<Compile Include="Parsers\LeafInputCsvParser.cs" />
|
||||||
<Compile Include="Utility\ParsedObjectFactory.cs" />
|
<Compile Include="Utility\ParsedObjectFactory.cs" />
|
||||||
<Compile Include="Utility\ParseException.cs" />
|
<Compile Include="Utility\ParseException.cs" />
|
||||||
|
<Compile Include="Utility\ParseInfoPropertyMatcher.cs" />
|
||||||
|
<Compile Include="Utility\ParseInfoPropertyMatcherWithCache.cs" />
|
||||||
<Compile Include="Utility\ReflectionExtensions.cs" />
|
<Compile Include="Utility\ReflectionExtensions.cs" />
|
||||||
<Compile Include="Utility\StringExtensions.cs" />
|
<Compile Include="Utility\StringExtensions.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace LeafWeb.Core.Parsers
|
|||||||
|
|
||||||
private IEnumerable<LeafGasComparison> ParseLeafGasComparisonSet(string[] fittingTitles)
|
private IEnumerable<LeafGasComparison> ParseLeafGasComparisonSet(string[] fittingTitles)
|
||||||
{
|
{
|
||||||
|
var matcher = new ParseInfoPropertyMatcher<LeafGasComparisonPhotosyntheticInfo>();
|
||||||
var endOfFile = false;
|
var endOfFile = false;
|
||||||
while (!endOfFile)
|
while (!endOfFile)
|
||||||
{
|
{
|
||||||
@@ -43,7 +44,7 @@ namespace LeafWeb.Core.Parsers
|
|||||||
throw new ParseException($"Encountered empty line while readding fitting info on line {CsvReader.Row}");
|
throw new ParseException($"Encountered empty line while readding fitting info on line {CsvReader.Row}");
|
||||||
if (values == null) // end of file
|
if (values == null) // end of file
|
||||||
yield break;
|
yield break;
|
||||||
if (ParsedObjectFactory<LeafGasComparisonPhotosyntheticInfo>.IsPropertiesTitlesMatch(values))
|
if (matcher.IsPropertiesTitlesMatch(values))
|
||||||
{
|
{
|
||||||
photosyntheticTitles = values;
|
photosyntheticTitles = values;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace LeafWeb.Core.Utility
|
||||||
|
{
|
||||||
|
public static class Memoizer
|
||||||
|
{
|
||||||
|
public static Func<A, R> ThreadsafeMemoize<A, R>(Func<A, R> f)
|
||||||
|
{
|
||||||
|
var cache = new ConcurrentDictionary<A, R>();
|
||||||
|
|
||||||
|
return argument => cache.GetOrAdd(argument, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<A1, A2, R> ThreadsafeMemoize<A1, A2, R>(Func<A1, A2, R> f)
|
||||||
|
{
|
||||||
|
var cache = new ConcurrentDictionary<Tuple<A1, A2>, R>();
|
||||||
|
|
||||||
|
return (a1, a2) => cache.GetOrAdd(Tuple.Create(a1, a2), t => f(t.Item1, t.Item2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Fasterflect;
|
||||||
|
|
||||||
|
namespace LeafWeb.Core.Utility
|
||||||
|
{
|
||||||
|
public class ParseInfoPropertyMatcher<T>
|
||||||
|
where T : new()
|
||||||
|
{
|
||||||
|
public IList<PropertyInfo> ParseInfoProperties { get; }
|
||||||
|
|
||||||
|
public ParseInfoPropertyMatcher()
|
||||||
|
{
|
||||||
|
ParseInfoProperties = GetParseInfoProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PropertyInfo[] GetParseInfoProperties()
|
||||||
|
{
|
||||||
|
var propertyInfos = typeof(T).Properties();
|
||||||
|
return propertyInfos.Where(p => AttributeExtensions.HasAttribute<ParseInfoAttribute>(p)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PropertyInfo MatchProperty(string title, int position)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
ParseInfoProperties.FirstOrDefault(p => p.Attribute<ParseInfoAttribute>().IsTitleMatch(title)) ??
|
||||||
|
ParseInfoProperties.FirstOrDefault(p => p.Attribute<ParseInfoAttribute>().IsPositionMatch(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PropertyInfo MatchPropertyExact(string title, int position)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
ParseInfoProperties
|
||||||
|
.FirstOrDefault(p =>
|
||||||
|
{
|
||||||
|
var attribute = p.Attribute<ParseInfoAttribute>();
|
||||||
|
return attribute.IsTitleMatch(title) && attribute.IsPositionMatch(position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsPropertiesTitlesMatch(string[] titles)
|
||||||
|
{
|
||||||
|
var propertyMatch = 0;
|
||||||
|
var propertyNoMatch = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < titles.Length; i++)
|
||||||
|
{
|
||||||
|
var title = titles[i];
|
||||||
|
var position = i + 1;
|
||||||
|
if (String.IsNullOrEmpty(title))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var property = MatchPropertyExact(title, position);
|
||||||
|
|
||||||
|
if (property != null)
|
||||||
|
propertyMatch++;
|
||||||
|
else
|
||||||
|
propertyNoMatch++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyMatch / (double)propertyNoMatch > .9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace LeafWeb.Core.Utility
|
||||||
|
{
|
||||||
|
public class ParseInfoPropertyMatcherWithCache<T> : ParseInfoPropertyMatcher<T>
|
||||||
|
where T: new()
|
||||||
|
{
|
||||||
|
private readonly Func<string, int, PropertyInfo> _matchPropertyMemo;
|
||||||
|
private readonly Func<string, int, PropertyInfo> _matchPropertyExactMemo;
|
||||||
|
|
||||||
|
public ParseInfoPropertyMatcherWithCache()
|
||||||
|
{
|
||||||
|
_matchPropertyMemo = Memoizer.ThreadsafeMemoize<string,int,PropertyInfo>(base.MatchProperty);
|
||||||
|
_matchPropertyExactMemo = Memoizer.ThreadsafeMemoize<string,int,PropertyInfo>(base.MatchPropertyExact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PropertyInfo MatchProperty(string title, int position)
|
||||||
|
{
|
||||||
|
return _matchPropertyMemo(title, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PropertyInfo MatchPropertyExact(string title, int position)
|
||||||
|
{
|
||||||
|
return _matchPropertyExactMemo(title, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,8 @@ using Fasterflect;
|
|||||||
|
|
||||||
namespace LeafWeb.Core.Utility
|
namespace LeafWeb.Core.Utility
|
||||||
{
|
{
|
||||||
public static class ParsedObjectFactory<T> where T : new()
|
public static class ParsedObjectFactory<T>
|
||||||
|
where T : new()
|
||||||
{
|
{
|
||||||
static ParsedObjectFactory()
|
static ParsedObjectFactory()
|
||||||
{
|
{
|
||||||
@@ -14,19 +15,13 @@ namespace LeafWeb.Core.Utility
|
|||||||
BoolTypeConverter.Register();
|
BoolTypeConverter.Register();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PropertyInfo[] GetProperties()
|
|
||||||
{
|
|
||||||
var propertyInfos = typeof(T).Properties();
|
|
||||||
return propertyInfos.Where(p => p.HasAttribute<ParseInfoAttribute>()).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create an object type T filling properties from the given title values
|
/// Create an object type T filling properties from the given title values
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="titleValues">Colon separated title: values</param>
|
/// <param name="titleValues">Colon separated title: values</param>
|
||||||
public static T Create(string[] titleValues)
|
public static T Create(string[] titleValues)
|
||||||
{
|
{
|
||||||
var properties = GetProperties();
|
var properties = new ParseInfoPropertyMatcherWithCache<T>().ParseInfoProperties;
|
||||||
var obj = new T();
|
var obj = new T();
|
||||||
// take each of the
|
// take each of the
|
||||||
for (var index = 0; index < titleValues.Length; index++)
|
for (var index = 0; index < titleValues.Length; index++)
|
||||||
@@ -68,7 +63,7 @@ namespace LeafWeb.Core.Utility
|
|||||||
|
|
||||||
public static T[] Create(string[] titles, string[][] valueArrays)
|
public static T[] Create(string[] titles, string[][] valueArrays)
|
||||||
{
|
{
|
||||||
var properties = GetProperties();
|
var matcher = new ParseInfoPropertyMatcherWithCache<T>();
|
||||||
var objs = new T[valueArrays.Length];
|
var objs = new T[valueArrays.Length];
|
||||||
|
|
||||||
for (var vIndex = 0; vIndex < valueArrays.Length; vIndex++)
|
for (var vIndex = 0; vIndex < valueArrays.Length; vIndex++)
|
||||||
@@ -84,7 +79,7 @@ namespace LeafWeb.Core.Utility
|
|||||||
if (IsMissingValue(value))
|
if (IsMissingValue(value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var property = MatchProperty(properties, title, position);
|
var property = matcher.MatchProperty(title, position);
|
||||||
|
|
||||||
if (property != null)
|
if (property != null)
|
||||||
{
|
{
|
||||||
@@ -101,7 +96,7 @@ namespace LeafWeb.Core.Utility
|
|||||||
|
|
||||||
public static T Create(Tuple<string, string>[] titleValues)
|
public static T Create(Tuple<string, string>[] titleValues)
|
||||||
{
|
{
|
||||||
var properties = GetProperties();
|
var matcher = new ParseInfoPropertyMatcherWithCache<T>();
|
||||||
var obj = new T();
|
var obj = new T();
|
||||||
|
|
||||||
for (var index = 0; index < titleValues.Length; index++)
|
for (var index = 0; index < titleValues.Length; index++)
|
||||||
@@ -114,7 +109,7 @@ namespace LeafWeb.Core.Utility
|
|||||||
if (IsMissingValue(value))
|
if (IsMissingValue(value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var property = MatchProperty(properties, title, position);
|
var property = matcher.MatchProperty(title, position);
|
||||||
|
|
||||||
if (property != null)
|
if (property != null)
|
||||||
{
|
{
|
||||||
@@ -132,50 +127,6 @@ namespace LeafWeb.Core.Utility
|
|||||||
return string.IsNullOrEmpty(value) || value == "NA" || value == "-9999";
|
return string.IsNullOrEmpty(value) || value == "NA" || value == "-9999";
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this call maybe could be cached
|
|
||||||
private static PropertyInfo MatchProperty(PropertyInfo[] properties, string title, int position)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
properties.FirstOrDefault(p => p.Attribute<ParseInfoAttribute>().IsTitleMatch(title)) ??
|
|
||||||
properties.FirstOrDefault(p => p.Attribute<ParseInfoAttribute>().IsPositionMatch(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this call maybe could be cached
|
|
||||||
private static PropertyInfo MatchPropertyExact(PropertyInfo[] properties, string title, int position)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
properties
|
|
||||||
.FirstOrDefault(p =>
|
|
||||||
{
|
|
||||||
var attribute = p.Attribute<ParseInfoAttribute>();
|
|
||||||
return attribute.IsTitleMatch(title) && attribute.IsPositionMatch(position);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPropertiesTitlesMatch(string[] titles)
|
|
||||||
{
|
|
||||||
var properties = GetProperties();
|
|
||||||
var propertyMatch = 0;
|
|
||||||
var propertyNoMatch = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < titles.Length; i++)
|
|
||||||
{
|
|
||||||
var title = titles[i];
|
|
||||||
var position = i + 1;
|
|
||||||
if (string.IsNullOrEmpty(title))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var property = MatchPropertyExact(properties, title, position);
|
|
||||||
|
|
||||||
if (property != null)
|
|
||||||
propertyMatch++;
|
|
||||||
else
|
|
||||||
propertyNoMatch++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return propertyMatch / (double) propertyNoMatch > .9;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryConvertValue(PropertyInfo property, object value, out object convertedValue)
|
private static bool TryConvertValue(PropertyInfo property, object value, out object convertedValue)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1036,6 +1036,9 @@
|
|||||||
<Name>Core</Name>
|
<Name>Core</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<WCFMetadata Include="Service References\" />
|
||||||
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
|||||||
Reference in New Issue
Block a user