不必要的 Java 上下文切换
我有一个 Java 线程网络(基于流的编程),通过固定容量通道进行通信 - 在 WindowsXP 下运行。根据我们对“绿色”线程(非抢占式)的经验,我们期望的是,如果通道变大,线程切换上下文的频率会降低(从而减少 CPU 时间)。然而,我们发现增加通道大小不会对运行时间产生任何影响。似乎正在发生的情况是,即使通道未满或未空(即即使线程不必挂起),Java 仍决定切换线程,这会消耗 CPU 时间,但没有明显的优势。此外,更改线程优先级不会产生任何明显的差异。
我的问题是,是否有某种方法可以说服Java不要进行不必要的上下文切换,而是推迟切换,直到确实需要切换线程——是否有某种方法可以改变Java的调度逻辑?还是它对我没有注意到的事情做出了反应?或者是否还有其他异步机制,例如线程工厂、Runnable,甚至可能是守护进程(!)。答案似乎并不明显,因为到目前为止,我的通讯员还没有给出答案(包括最近的两位计算机科学教授)。或者也许我遗漏了一些非常明显的东西,以至于人们无法想象我不知道它...
我在这里添加了发送和接收代码 - 不是很优雅,但它似乎有效...;-) 在如果您想知道,我认为“发送”中的 goLock 逻辑可能会导致问题,但暂时删除它并没有任何区别。我已经添加了发送和接收的代码...
public Synchronized Packet receive() {
if (isDrained()) {
return null;
}
while (isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
close();
return null;
}
if (isDrained()) {
return null;
}
}
if (isDrained()) {
return null;
}
if (isFull()) {
notifyAll(); // notify other components waiting to send
}
Packet packet = array[receivePtr];
array[receivePtr] = null;
receivePtr = (receivePtr + 1) % array.length;
//notifyAll(); // only needed if it was full
usedSlots--;
packet.setOwner(receiver);
if (null == packet.getContent()) {
traceFuncs("Received null packet");
} else {
traceFuncs("Received: " + packet.toString());
}
return packet;
}
Synchronized boolean send(final Packet packet, Final OutputPort op) {
sender = op.sender;
if (isClosed()) {
return false;
}
while (isFull()) {
try {
wait();
} catch (InterruptedException e) {
indicateOneSenderClosed();
return false;
}
sender = op.sender;
}
if (isClosed()) {
return false;
}
try {
receiver.goLock.lockInterruptibly();
} catch (InterruptedException ex) {
return false;
}
try {
packet.clearOwner();
array[sendPtr] = packet;
sendPtr = (sendPtr + 1) % array.length;
usedSlots++; // move this to here
if (receiver.getStatus() == StatusValues.DORMANT || receiver.getStatus() == StatusValues.NOT_STARTED) {
receiver.activate(); // start or wake up if necessary
} else {
notifyAll(); // notify receiver
// other components waiting to send to this connection may also get
// notified,
// but this is handled by while statement
}
sender = null;
Component.network.active = true;
} finally {
receiver.goLock.unlock();
}
return true;
}
感谢问!我一直在 Sun 论坛上讨论同样的问题,这是我在该论坛上的最后一篇文章:
我们现在最好的猜测是这种效果是由 Windows 造成的 调度逻辑。
微软似乎承认这个领域需要一些改进 在介绍 UMS 时 - 我引用:“建议应用程序使用 UMS 具有高性能要求,需要高效运行许多 多处理器或多核系统上并发的线程。 ...UMS 是 从 64 位版本的 Windows 7 和 Windows 开始可用 服务器2008 R2。此功能在 32 位版本上不可用 Windows。”希望 Java 能够在以后的某些应用中利用 UMS 发布。
感谢您的帮助!
I have a network of Java Threads (Flow-Based Programming) communicating via fixed-capacity channels - running under WindowsXP. What we expected, based on our experience with "green" threads (non-preemptive), would be that threads would switch context less often (thus reducing CPU time) if the channels were made bigger. However, we found that increasing channel size does not make any difference to the run time. What seems to be happening is that Java decides to switch threads even though channels aren't full or empty (i.e. even though a thread doesn't have to suspend), which costs CPU time for no apparent advantage. Also changing Thread priorities doesn't make any observable difference.
My question is whether there is some way of persuading Java not to make unnecessary context switches, but hold off switching until it is really necessary to switch threads - is there some way of changing Java's dispatching logic? Or is it reacting to something I didn't pay attention to?! Or are there other asynchronism mechanisms, e.g. Thread factories, Runnable(s), maybe even daemons (!). The answer appears to be non-obvious, as so far none of my correspondents has come up with an answer (including most recently two CS profs). Or maybe I'm missing something that's so obvious that people can't imagine my not knowing it...
I've added the send and receive code here - not very elegant, but it seems to work...;-) In case you are wondering, I thought the goLock logic in 'send' might be causing the problem, but removing it temporarily didn't make any difference. I have added the code for send and receive...
public synchronized Packet receive() {
if (isDrained()) {
return null;
}
while (isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
close();
return null;
}
if (isDrained()) {
return null;
}
}
if (isDrained()) {
return null;
}
if (isFull()) {
notifyAll(); // notify other components waiting to send
}
Packet packet = array[receivePtr];
array[receivePtr] = null;
receivePtr = (receivePtr + 1) % array.length;
//notifyAll(); // only needed if it was full
usedSlots--;
packet.setOwner(receiver);
if (null == packet.getContent()) {
traceFuncs("Received null packet");
} else {
traceFuncs("Received: " + packet.toString());
}
return packet;
}
synchronized boolean send(final Packet packet, final OutputPort op) {
sender = op.sender;
if (isClosed()) {
return false;
}
while (isFull()) {
try {
wait();
} catch (InterruptedException e) {
indicateOneSenderClosed();
return false;
}
sender = op.sender;
}
if (isClosed()) {
return false;
}
try {
receiver.goLock.lockInterruptibly();
} catch (InterruptedException ex) {
return false;
}
try {
packet.clearOwner();
array[sendPtr] = packet;
sendPtr = (sendPtr + 1) % array.length;
usedSlots++; // move this to here
if (receiver.getStatus() == StatusValues.DORMANT || receiver.getStatus() == StatusValues.NOT_STARTED) {
receiver.activate(); // start or wake up if necessary
} else {
notifyAll(); // notify receiver
// other components waiting to send to this connection may also get
// notified,
// but this is handled by while statement
}
sender = null;
Component.network.active = true;
} finally {
receiver.goLock.unlock();
}
return true;
}
thanks for asking! I have been discussing the same question on the Sun forum, and here is my last post on that forum:
Our best guess right now is that this effect results from Windows'
scheduling logic.Microsoft seems to be acknowledging that this area needs some improvement
as it is introducing UMS - I quote: "UMS is recommended for applications
with high performance requirements that need to efficiently run many
threads concurrently on multiprocessor or multicore systems. ... UMS is
available starting with 64-bit versions of Windows 7 and Windows
Server 2008 R2. This feature is not available on 32-bit versions of
Windows." Hopefully, Java will take advantage of UMS in some later
release.
Thanks for your help!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我有点尴尬 - 今天下午我突然想到,也许我担心性能的网络太简单了,因为我只有两个进程es,还有两个进程或者。因此,Windows 可能一直在努力保持处理器平衡!所以我想知道如果我给 Windows 提供很多进程会发生什么。
我设置了两个网络:
a)50个生成组件馈送50个丢弃组件 - 即高度并行网络 - 所以总共有100个线程
b)50个生成组件馈送1个丢弃组件 - 即高度“漏斗”网络 - 所以这是
我运行的 51个线程每一次运行 6 次,连接容量为 10,每一次运行 6 次,连接容量为 100。每次运行总共生成 50 * 20,000 个信息包,总共 1,000,000 个数据包,运行时间约为 1 分钟
。 4个案例的平均值:
a) 连接能力为 10 - 59.151 秒。
a) 连接能力为 100 - 52.008 秒。
b) 连接能力为 10 - 76.745 秒。
b) 连接能力为 100 - 60.667 秒。
所以看来连接能力确实有影响!而且,看起来 JavaFBP 的表现相当不错...我为有点仓促而道歉 - 但也许它让我们大家更深入地思考多核机器中的多线程...;-)
再次道歉,并感谢大家谁对这个主题贡献了想法!
I'm a bit embarrassed - it suddenly occurred to me this afternoon that maybe the network whose performance I was worried about was just too simple, as I only had two processes, and two processors. So Windows may have been trying too hard to keep the processors balanced! So I wondered what would happen if I gave Windows lots of processes.
I set up two networks:
a) 50 Generate components feeding 50 Discard components - i.e. highly parallel network - so that's 100 threads in total
b) 50 Generate components feeding 1 Discard component - i.e. highly "funnelled" network - so that's 51 threads
I ran each one 6 times with a connection capacity of 10, and 6 times with a connection capacity of 100. Every run generated a total of 50 * 20,000 information packets, for a total of 1,000,000 packets, and ran for about 1 minute..
Here are the averages of the 4 cases:
a) with connection capacity of 10 - 59.151 secs.
a) with connection capacity of 100 - 52.008 secs.
b) with connection capacity of 10 - 76.745 secs.
b) with connection capacity of 100 - 60.667 secs.
So it looks like the connection capacity does make a difference! And, it looks like JavaFBP performs reasonably well... I apologize for being a bit hasty - but maybe it made us all think a bit more deeply about multithreading in a multicore machine... ;-)
Apologies again, and thanks to everyone who contributed thoughts on this topic!
抱歉,如果这完全是假的,但我很确定自 Java 1.1 以来 Java 不再使用绿色线程了。至少维基百科也是这么说的。
这会限制您使用优先级 - 但在大多数情况下,我也无法实现可观察到的性能改进。
Sorry, if this is total bogus, but I am pretty sure that Java doesn't do green threads anymore since Java 1.1. At least Wikipedia says so, too.
This would limit you to use priorities - but in most of the cases I couldn't achieve observable performance improvements either.
绿色线程消失了(也许 Solaris 仍然支持它,但我对此表示怀疑)。此外,Java 不会切换线程,而是操作系统会切换线程。 Java 所做的唯一一件事就是通过使用操作系统函数向操作系统发出线程处于空闲/等待/阻塞状态的信号。因此,如果您的程序遇到任何同步点,即 Thread.wait/sleep,它会发出信号,表明它不再需要 cpu。
除此之外,操作系统维护时间片,并会从一个线程中夺走CPU,即使这样,当其他线程等待CPU时,它仍然可以运行。
您可以在这里发布更多代码吗?
Green threads are gone (maybe Solaris supports it still but I doubt that). Additionally Java does not switch threads, the OS does that. The only thing Java does is signalling to the OS, that a thread is idle/waits/blocks by using OS functions. So if your program hits any synchronisation points, does Thread.wait/sleep, it will signal, that it does not need the cpu anymore.
Besides that, the OS maintains time slices and will take away the cpu from a thread, even so it could still run, when other threads wait for the cpu.
Can you publish some more code here?