我有一个存储过程,它返回几个结果集和1000行的(一些)结果集中。我同时使用x个线程执行这个存储过程,最大并发y个线程。
当我刚刚浏览数据时:
using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
do
{
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
var value = reader.GetValue(i);
}
}
}
while (reader.NextResult());
}
我得到了合理的吞吐量和CPU。现在显然这是无用的,所以我需要一些对象!好的,现在我把它改成这样:
using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
while(reader.Read())
{
Bob b = new Bob(reader);
this.bobs.Add(b);
}
reader.NextResult();
while(reader.Read())
{
Clarence c = new Clarence(reader);
this.clarences.Add(c);
}
// More fun
}
以及我的数据类实现:
public Bob(SqlDataReader reader)
{
this.some = reader.GetInt32(0);
this.parameter = reader.GetInt32(1);
this.value = reader.GetString(2);
}
而且这种情况更糟。这并不奇怪。令人惊讶的是,CPU下降了,下降了大约20%到25%(即不是它使用的25%;它接近它使用的50%)!为什么要做更多的工作,降低CPU?我不明白……看起来某个地方有锁——但我不明白在哪里?我想充分利用这台机器!
EDIT-由于演示代码示例不正确而更改了代码。哎呀。
另外:为了测试构造函数理论,我更改了创建对象的实现。这一次,它还对字段执行for循环,并执行getValue,并将创建一个空对象。所以它并没有达到我想要的效果,是的,但我想看看创建大量对象是否是问题所在。事实并非如此。
第二次编辑:看起来把对象添加到列表中是个问题——一旦我把这些对象添加到名单中,CPU就会立即下降。不知道如何改进。。。(或者是否值得研究;第一种情况显然很愚蠢)
我可以看到您的方法论的性能问题,但我不确定我能找出解释原因的确切方法。但本质上,您是在将光标流添加到实例化中,而不是设置属性。如果我猜测一下,当你作为构造函数的一部分从读取器中提取时,你会导致一些上下文切换。
如果你把阅读器想象成一个消防软管光标(确实如此),并想到由拿着软管的用户控制的消防软管(正常方法)与正在填充的容器之间的区别,你就开始了解问题了。
不确定线程是如何关联的?但是,如果你有多个客户端,并且你通过将比特移到构造函数而不是在构造的对象上设置属性来更多地停止软管的流动,然后在多个请求之间争夺线程时间,我可以想象在负载下会出现更大的问题。
好吧,CPU没有达到最大值的原因只是它在等待其他东西。磁盘或网络。
考虑到您的第一个代码块只是读取并立即丢弃一个值,有几种可能性:一种是编译器可能正在优化值变量的分配,因为它的范围有限,从未使用过。这意味着处理器不必等待内存分配。相反,它主要受到从网络中获取数据的速度的限制。
通常情况下,内存分配应该非常快,但是,如果你分配的内存量导致窗口将内容推到磁盘上,那么这会受到硬盘速度的影响。
查看您的第二个代码块,您正在构建大量对象(更多的内存使用量)并保留它们。两者都不允许编译器优化调用;并且这两者都对不仅仅是处理器的本地系统资源施加了更大的压力。
为了确定以上是否接近,你需要将手表计数器放在应用程序使用的内存量与可用RAM量之间。此外,您还需要磁盘系统上的计数器来查看在任何一种情况下它是否被大量访问。
重点是,当代码块运行时,试着观察机器中发生的一切。这将使您更好地了解处理器等待的原因。
也许这只是一个合并Bob和Clarence调用的问题,这些调用本身就不太占用CPU,而且由于I/O瓶颈或类似问题,执行的这部分只需要一些时间。
您最好的选择是通过探查器运行此操作,并查看报告。