哪种编程方法更好?为什么?
我有一个这样的课程:
class data {
public double time { get; internal set; }
public double count { get; internal set; }
public average_count { ... }
}
其中average_count应为read_only并计算计数/时间。
将访问者编写为:
是否更好public average_count { get {
return (time == 0) ? 0 : (count / time);
}}
或者我应该做点什么:
private _avg_count;
public average_count {
get
{
return _avg_count;
}
internal set
{
return _avg_count;
}
}
在时间和计数集访问器中更新_avg_count的位置?
似乎第一个更易于阅读,但如果经常访问average_count则可能会更慢。编译器优化是否会使差异无关紧要?
答案 0 :(得分:13)
动态执行此操作会产生更易读的代码。预先计算可以提高性能,但是只有在(a)必要时和(b)您进行了分析并且它会产生影响时才应该这样做。
最重要的是,可读性和可维护性应该在绝对必要时牺牲性能 。
答案 1 :(得分:5)
这是一个看似简单的问题,但几乎不可能回答。原因在于“正确”取决于许多因素。
在他的回答Skilldrick中,建议您更喜欢可读性优于一般规则的内容:
[R]可扩展性和可维护性应该 仅因性能而牺牲 什么时候绝对必要。
我会反驳说,在典型的商业应用程序中仅为真,其中性能和功能是两个明显可区分的功能。在某些高性能软件场景中,这并不是一件容易的事情,因为性能和功能可能会变得密不可分 - 也就是说,如果程序完成任务的程度取决于它的执行情况(这种情况就是如此)我目前的工作地点,一家从事算法交易的公司。)
所以这是你的判断。最好的建议是在你有一个问题时进行分析;如果在你的情况下牺牲性能的可读性是合适的,那么你应该这样做。
0xA3提出了一种相当优雅的方法,可以对各种类型进行折衷:只根据需要计算值,然后对其进行缓存。
此方法的缺点当然是需要更多内存来维护。 int?
需要与int
加上bool
基本相同的内存量(由于对齐问题,实际上可能意味着64位而不是40位)。如果你有这个data
类的实例的加载和加载,并且内存是你所瞄准的平台上的稀缺资源,那么每个实例的另外32位膨胀你的类型可能不是最聪明的举动。
那就是说,我一般都同意其他人所说的,在其他条件相同的情况下,你最好在可读性方面犯错误,以便在将来重新审视时能够理解你的代码。但是,解决这个问题的各种方法都没有特别复杂。
最重要的是,只有你知道自己的具体情况,因此你才能最好地决定在这里做什么。
答案 2 :(得分:4)
具体取决于您拨打average_count
的频率以及count
和time
的修改频率。对于这种优化,我建议你使用profiler。
答案 3 :(得分:4)
如果性能很关键并且您需要经常访问该属性,那么您还有另一个选项:在需要时计算结果,然后缓存结果。模式看起来像这样:
class Foo
{
int? _cachedResult = null;
int _someProperty;
public int SomeProperty
{
get { return _someProperty; }
set { _someProperty = value; _cachedResult = null; }
}
int _someOtherProperty;
public int SomeOtherProperty
{
get { return _someOtherProperty; }
set { _someOtherProperty = value; _cachedResult = null; }
}
public int SomeDerivedProperty
{
get
{
if (_cachedResult == null)
_cachedResult = someExpensiveCalculation();
return (int)_cachedResult;
}
}
}
答案 4 :(得分:1)
在这个简单的例子中,我肯定会首先实现计算版本;然后根据需要进行优化。如果存储该值,则还需要额外的代码来重新计算值,如果它所依赖的任何值发生更改,则会导致出现错误。
不要过早优化。
答案 5 :(得分:1)
我会选择你的第一个选择。这些是内存中的对象,因此对你正在做的事情的计算将非常快。此外,如果您为此创建了一个专用属性(例如,average_count),那么您将不得不添加 more 代码,以便在setter中重新计算时间和计数。< / p>
作为附注(因为您询问最佳实践),您应该在C#中使用Pascal大小写,这是初始大写且没有下划线。
答案 6 :(得分:0)
编译器优化是否会使差异无关紧要?
取决于您认为“重要”的内容。阅读变量很快。划分两个数字相当快。实际上,根据RAM缓存中的内容,读取变量可能需要比分割时间更长的时间。
使用第一种方法。如果它看起来很慢,那么考虑第二个。
答案 7 :(得分:0)
如果您关心线程安全,那么第二个选项可能比第一个选项更容易。
private double _avg_count;
static readonly object avgLock = new object();
public double time { get; internal set; }
public double count { get; internal set; }
public double average_count {
get
{
return _avg_count;
}
}
private void setAverageCount()
{
_avg_count = time == 0 ? 0 : (count / time);
}
public void Increment()
{
lock (avgLock)
{
count += 1;
setAverageCount();
}
}
public void EndTime(double time)
{
lock (avgLock)
{
time = time;
setAverageCount();
}
}