我不确定是否需要在这里或在https://softwareengineering.stackexchange.com/上问这个问题,但是让我从目前的情况开始。
我正在维护一个数据转换器,该数据转换器逐条记录地转换记录以及其中的许多记录(30M +)。 我从旧数据库收到一个ID,插入后我有了新的主键。这些密钥将存储在某种词典中,因此当我需要查找新ID时就可以找到它。 这是通过以下代码(简化的示例代码,而不是真实的代码)完成的
public class PersonConverter : Converter
{
public bool Convert(int oldPersonId /*some more parameters*/)
{
int newPersonId;
try
{
newPersonId = GetNewPersonIdForPerson(oldPersonId);
}
catch (SourceKeyNotFoundException)
{
SomeLogging("Log message");
return true;
}
//lots of more thing are happening here!
return true;
}
}
public class Converter
{
private Dictionary<int,int> _convPersonId;
protected int GetNewPersonIdForPerson(int oldPersonId)
{
int key = oldPersonId;
if (_convPersonId.TryGetValue(key, out int newPersonId))
return newPersonId;
throw new SourceKeyNotFoundException(key.ToString());
}
protected void SomeLogging(string message)
{
//Implement logging etc...
}
}
在PersonConverter
中,我们只有一个对GetNewPersonIdForPerson(oldPersonId)
的调用,但是在实际代码中,有许多这样的调用是针对不同的字典的。如您所见,我的前任很喜欢抛出异常。在性能方面,这并不理想,根据Microsoft网站上关于Exceptions & Performance的说法,他们建议使用Tester-Doer Pattern
或Try-Parse pattern
解决方案1
我想出的解决方案是让GetNewPersonIdForPerson(oldPersonId)
返回一个int.MinValue
或其他确定性值,而不是try/catch block
使用if/else block
来检查该值。
抢劫新方法GetNewPersonIdForPerson2(int oldPersonId)
protected int GetNewPersonIdForPerson2(int oldPersonId)
{
int key = oldPersonId;
if (_convPersonId.TryGetValue(key, out int newPersonId))
return newPersonId;
return int.MinValue;
}
,以及我在try/catch block
的{{1}}方法内调用Convert
的方式
PersonConverter
此解决方案存在一些问题,因为我需要两次调用if(GetNewPersonIdForPerson2(oldPersonId) != int.MinValue)
{
newPersonId = GetNewPersonIdForPerson(oldPersonId);
}
else
{
SomeLogging("Log message");
return true;
}
事件,尽管我认为从性能角度来看,这比抛出异常要快。
解决方案2
另一种解决方案是在GetNewPersonIdForPerson2
方法上使用out变量,如下所示:
GetNewPersonIdForPerson
然后在PersonConverter的Convert方法中进行以下操作
protected bool GetNewPersonIdForPerson3(int oldPersonId, out int returnPersonId)
{
int key = oldPersonId;
if (_convPersonId.TryGetValue(key, out returnPersonId))
return true;
return false;
}
我没有做任何喷射来重构它,因为我想要一些关于最佳解决方案的信息。我更喜欢if (!GetNewPersonIdForPerson3(oldPersonId, out newPersonId))
{
SomeLogging("Log message");
return true;
}
,因为它更易于重构,但是在同一字典中有2个查询。我无法确切地说出我有多少Solution 1
,但是有很多。而且Try/Catch blocks
方法并不是我唯一(20+)不懂得的方法。
谁能告诉我解决这个问题的好的模式,或者是我提出的最佳解决方案之一。
PS:进行大转换时,根据性能计数器GetNewPersonIdForPerson
会抛出750万以上的感知。
PS 2:这只是一些示例代码,与该示例不同,词典永远存在。
答案 0 :(得分:1)
如果您要使用大部分或全部Persons
,最好在启动应用程序时(或其他运行良好的时间)以初始加载方式加载所有用户,并且然后在每次需要时快速查找。
明智的TryGetValue
速度很快,您无需进行任何优化。请在此处查看基准:What is more efficient: Dictionary TryGetValue or ContainsKey+Item?
在正常情况下,Try-Catch
不应是此线程中讨论的那样昂贵的性能:
通常,在今天的实现中,输入try块不是 太贵了(并非总是如此)。但是,投掷和 处理异常通常是相对昂贵的操作。所以, 通常应将异常用于异常事件,而不是正常事件 流控制。
我们期望GetNewPersonIDForPerson()
多久失败一次?
有了这些信息,我将使用类似于以下内容的东西:
public class PersonConverter : Converter
{
private Dictionary<int, int> _IDs;
public PersonConverter()
{
this._IDs = new Dictionary<int, int>();
}
public int Convert(int oldPersonID)
{
int newPersonID = int.MinValue;
if (this._IDs.TryGetValue(oldPersonID, out newPersonID))
{
/* This oldPerson has been looked up before.
* The TryGetValue is fast so just let's do that and return the newPersonID */
return newPersonID;
}
else
{
try
{
/* This oldPerson has NOT been looked up before
* so we need to retrieve it from out source and update
* the dictionary */
int newPersonID = GetNewPersonIDForPerson(oldPersonID);
this._IDs.Add(oldPersonID, newPersonID);
return newPersonID;
}
catch (SourceKeyNotFoundException)
{
throw;
}
}
}
}
但是,如果您仍然担心Try-Catch
语句,我可能会检查GetNewPersonIDForPerson
方法。如果它执行对数据库的查询,则如果oldPersonID
的记录不存在,并可能根据该数据创建我的逻辑-Tester-Doer
-instad,则可能会返回一些值(0或-1) Try-Catch
的使用。
在这种情况下,我可能还会进行基准测试,以查看使用Try-Catch
语句是否有很大的不同。如果执行时间有很大的差异,我会使用最快的,如果可以忽略,我会使用最容易理解和维护的代码。
很多时间,我们尝试进行优化,但这确实不值得。