Performance/Code-readability 访问 object 的字段与复制
Performance/Code-readability of accessing object's field vs making a copy
我很难准确解释我在标题中的意思,所以我会马上提供代码示例。
看看下面的 C# 代码:
internal static async Task OnGroupGiveawayCreated(Giveaway giveaway) {
if (giveaway == null) {
return;
}
ulong creatorID = giveaway.CreatorID;
if (creatorID == 0 || !await MySQL.UserExists(creatorID).ConfigureAwait(false)) {
return;
}
// Date
DateTime today = DateTime.Now;
if (today.Month == 9 && today.Day == 9) {
await MySQL.InsertAchievement(40, creatorID).ConfigureAwait(false);
await OnAchievementGot(creatorID).ConfigureAwait(false);
}
}
在上面的示例中,我决定将 giveaway.CreatorID
的副本作为局部变量,以省略每次访问时解析 object 字段的需要。最坏情况下一共访问了4次
上面也可以不用那样写:
internal static async Task OnGroupGiveawayCreated(Giveaway giveaway) {
if (giveaway == null) {
return;
}
if (giveaway.CreatorID == 0 || !await MySQL.UserExists(giveaway.CreatorID).ConfigureAwait(false)) {
return;
}
// Date
DateTime today = DateTime.Now;
if (today.Month == 9 && today.Day == 9) {
await MySQL.InsertAchievement(40, giveaway.CreatorID).ConfigureAwait(false);
await OnAchievementGot(giveaway.CreatorID).ConfigureAwait(false);
}
}
考虑到性能和可读性这两个因素,我想知道哪种风格更 preferred/recommended。
- [性能] 在第二个例子中,编译器是否足够聪明,注意到
giveaway
object 只能由一个线程访问,因此访问 giveaway.CreatorID
不必解决每次通话,但仅限于第一个?是否有任何建议使用第一种或第二种方法performance-wise?我知道类似的事情正在做,例如在 for
循环中,当我们使用例如Length
属性 的字符串(编译为 CIL 时,字符串的长度只计算一次)。如果编译器不够聪明,无法做到这一点,明智的做法是将其也存储在变量中。
- [可读性]您更喜欢使用哪种方式?选择是否取决于我们访问给定字段的次数?如果该字段被访问 2 次或更多次,我倾向于制作该字段的副本。这是错的吗?很长的 "chains",比如
giveaway.CreatorID.Achievements.Count
呢?
- 在决定使用第一种方法还是第二种方法之前,我还应该考虑其他因素吗?也许我问的问题根本不重要,因为答案应该是显而易见的?
很抱歉,如果我问的完全是胡说八道,那根本不重要,但有时我喜欢听听其他程序员对给定主题的看法,所以我可以在以后改进我的代码风格。现在,如果访问次数超过 1 次,我倾向于复制该字段,但我不知道是否需要(从性能方面)或推荐(从代码可读性方面)。
提前致谢。
使用临时变量作为微优化没有意义,但作为可读性改进确实有意义。
除非Giveaway
是一个struct
,编译器不可能知道对象只被一个线程访问。但是,它不需要知道这一点,因为它假设无论如何都会发生这种情况:除非 CreatorID
被标记为 volatile
,否则编译器保留只访问它一次并使用缓存副本的权利在随后的调用中。这是一个很长的说法 "the compiler is smart enough to make a copy for you"
制作命名副本的一个重要的可读性结果是它可以避免 reader 水平滚动。这很重要,因为水平滚动会减慢您的 readers。如果你可以给一个变量一个简短的名字而不会发疯,那就去吧。 (creatorID
很好;cid
可能描述不够)。
我很难准确解释我在标题中的意思,所以我会马上提供代码示例。
看看下面的 C# 代码:
internal static async Task OnGroupGiveawayCreated(Giveaway giveaway) {
if (giveaway == null) {
return;
}
ulong creatorID = giveaway.CreatorID;
if (creatorID == 0 || !await MySQL.UserExists(creatorID).ConfigureAwait(false)) {
return;
}
// Date
DateTime today = DateTime.Now;
if (today.Month == 9 && today.Day == 9) {
await MySQL.InsertAchievement(40, creatorID).ConfigureAwait(false);
await OnAchievementGot(creatorID).ConfigureAwait(false);
}
}
在上面的示例中,我决定将 giveaway.CreatorID
的副本作为局部变量,以省略每次访问时解析 object 字段的需要。最坏情况下一共访问了4次
上面也可以不用那样写:
internal static async Task OnGroupGiveawayCreated(Giveaway giveaway) {
if (giveaway == null) {
return;
}
if (giveaway.CreatorID == 0 || !await MySQL.UserExists(giveaway.CreatorID).ConfigureAwait(false)) {
return;
}
// Date
DateTime today = DateTime.Now;
if (today.Month == 9 && today.Day == 9) {
await MySQL.InsertAchievement(40, giveaway.CreatorID).ConfigureAwait(false);
await OnAchievementGot(giveaway.CreatorID).ConfigureAwait(false);
}
}
考虑到性能和可读性这两个因素,我想知道哪种风格更 preferred/recommended。
- [性能] 在第二个例子中,编译器是否足够聪明,注意到
giveaway
object 只能由一个线程访问,因此访问giveaway.CreatorID
不必解决每次通话,但仅限于第一个?是否有任何建议使用第一种或第二种方法performance-wise?我知道类似的事情正在做,例如在for
循环中,当我们使用例如Length
属性 的字符串(编译为 CIL 时,字符串的长度只计算一次)。如果编译器不够聪明,无法做到这一点,明智的做法是将其也存储在变量中。 - [可读性]您更喜欢使用哪种方式?选择是否取决于我们访问给定字段的次数?如果该字段被访问 2 次或更多次,我倾向于制作该字段的副本。这是错的吗?很长的 "chains",比如
giveaway.CreatorID.Achievements.Count
呢? - 在决定使用第一种方法还是第二种方法之前,我还应该考虑其他因素吗?也许我问的问题根本不重要,因为答案应该是显而易见的?
很抱歉,如果我问的完全是胡说八道,那根本不重要,但有时我喜欢听听其他程序员对给定主题的看法,所以我可以在以后改进我的代码风格。现在,如果访问次数超过 1 次,我倾向于复制该字段,但我不知道是否需要(从性能方面)或推荐(从代码可读性方面)。
提前致谢。
使用临时变量作为微优化没有意义,但作为可读性改进确实有意义。
除非Giveaway
是一个struct
,编译器不可能知道对象只被一个线程访问。但是,它不需要知道这一点,因为它假设无论如何都会发生这种情况:除非 CreatorID
被标记为 volatile
,否则编译器保留只访问它一次并使用缓存副本的权利在随后的调用中。这是一个很长的说法 "the compiler is smart enough to make a copy for you"
制作命名副本的一个重要的可读性结果是它可以避免 reader 水平滚动。这很重要,因为水平滚动会减慢您的 readers。如果你可以给一个变量一个简短的名字而不会发疯,那就去吧。 (creatorID
很好;cid
可能描述不够)。