【问题标题】:How to make a fast context switch from one process to another?如何快速从一个进程切换到另一个进程?
【发布时间】:2012-07-08 20:40:13
【问题描述】:

我需要在沙盒进程上运行不安全的本机代码,我需要减少进程切换的瓶颈。两个进程(控制器和沙盒)共享两个自动重置events 和用于通信的映射文件(共享内存)的一致视图。

为了使本文更小,我从示例代码中删除了初始化,但事件由控制器创建,使用 DuplicateHandle 复制,然后在工作前发送到沙盒进程。

控制器来源:

void inSandbox(HANDLE hNewRequest, HANDLE hAnswer, volatile int *shared) {
  int before = *shared;
  for (int i = 0; i < 100000; ++i) {
    // Notify sandbox of a new request and wait for answer.
    SignalObjectAndWait(hNewRequest, hAnswer, INFINITE, FALSE);
  }
  assert(*shared == before + 100000);
}

void inProcess(volatile int *shared) {
  int before = *shared;
  for (int i = 0; i < 100000; ++i) {
    newRequest(shared);
  }
  assert(*shared == before + 100000);
}

void newRequest(volatile int *shared) {
  // In this test, the request only increments an int.
  (*shared)++;
}

沙盒来源:

void sandboxLoop(HANDLE hNewRequest, HANDLE hAnswer, volatile int *shared) {
  // Wait for the first request from controller.
  assert(WaitForSingleObject(hNewRequest, INFINITE) == WAIT_OBJECT_0);
  for(;;) {
    // Perform request.
    newRequest(shared);
    // Notify controller and wait for next request.
    SignalObjectAndWait(hAnswer, hNewRequest, INFINITE, FALSE);
  }
}

void newRequest(volatile int *shared) {
  // In this test, the request only increments an int.
  (*shared)++;
}

测量:

  • inSandbox() - 550 毫秒,约 350k 上下文切换,42% CPU(25% 内核,17% 用户)。
  • inProcess() - 20 毫秒,约 2k 上下文切换,55% CPU(2% 内核,53% 用户)。

机器是Windows 7 Pro,Core 2 Duo P9700,8GB内存。

一个有趣的事实是,沙盒解决方案使用 42% 的 CPU 与 55% 的进程内解决方案。另一个值得注意的事实是,沙盒解决方案包含 350k 上下文切换,这比我们可以从源代码中推断出的 200k 上下文切换要多得多。

我需要知道是否有办法减少将控制权转移到另一个进程的开销。我已经尝试使用管道而不是事件,而且情况更糟。我还尝试完全不使用任何事件,方法是在每个请求上进行沙盒调用SuspendThread(GetCurrentThread()) 并让控制器调用ResumeThread(hSandboxThread),但性能类似于使用事件。

如果您有使用程序集(例如执行手动上下文切换)或 Windows 驱动程序工具包的解决方案,也请告诉我。我不介意安装驱动程序以加快速度。

我听说 Google Native Client 做了类似的事情,但我只找到了this documentation。如果您有更多信息,请告诉我。

【问题讨论】:

  • 可能没有任何方法可以显着加快这一速度。你能解释一下你的两个进程在做什么吗?也许还有另一种更有效的方法。
  • 您是否尝试过使用 RPC(具有 lrpc 端点类型)?
  • 这是一个移植到 Java 的旧应用程序,需要运行遗留的本机代码。本机代码不安全并导致 JVM 崩溃。这就是为什么我们希望它在沙箱中运行。沙盒是使用单独的进程实现的,但我们可能会使用不同的解决方案。
  • 我没有尝试 RPC/lrpc。你能发一些关于 lrpc 的链接吗?
  • 在生产系统中,沙盒和控制器可能会在上下文切换之间做更多的工作,因此每次切换 5.5 微秒的开销可能并不那么重要。

标签: windows multithreading assembly synchronization driver


【解决方案1】:

首先要尝试的是提高等待线程的优先级。这应该会减少无关上下文切换的数量。

另外,由于您使用的是 2 核系统,因此使用 spinlocks 代替事件将使您的代码更快,但以系统性能和功耗为代价:

void inSandbox(volatile int *lock, volatile int *shared) 
{
  int i, before = *shared;
  for (i = 0; i < 100000; ++i) {
    *lock = 1;
    while (*lock != 0) { }
  }
  assert(*shared == before + 100000);
}

void newRequest(volatile int *shared) {
  // In this test, the request only increments an int.
  (*shared)++;
}

void sandboxLoop(volatile int *lock, volatile int * shared)
{
  for(;;) {
    while (*lock != 1) { }
    newRequest(shared);
    *lock = 0;
  }
}

在这种情况下,您可能应该设置线程关联掩码和/或降低旋转线程的优先级,使其不会与繁忙的线程竞争 CPU 时间。

理想情况下,您应该使用混合方法。当一方要忙一段时间时,让另一方等待一个事件,以便其他进程可以获得一些 CPU 时间。您可以提前一点触发事件(使用自旋锁保持同步),以便其他线程在您准备好时准备好。

【讨论】:

  • 太棒了,我得到了大约 50 毫秒!几乎正在进行中的性能!非常感谢!
  • 顺便说一句,混合解决方案实施起来有点棘手 - 我不得不使用 InterlockedCompareExchange 来避免一些竞争条件。
猜你喜欢
  • 2021-03-21
  • 1970-01-01
  • 2013-04-13
  • 1970-01-01
  • 2020-09-27
  • 2011-05-02
  • 2013-05-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多