使用 C++ 的 Windows

Visual C++ 2010 和 </a0>-并行模式库

Kenny Kerr

本专栏基于 Visual Studio 2010 的预发布版本。 如更改,恕所有信息。

内容

语言增强功能
并行算法
任务和任务组

Visual C++ 2010 版 Visual Studio 中获取主要升级。 许多新的语言和库功能旨在仅仅是为使其更容易、 更自然表示您在代码中的愿望。 但这些功能的组合为一直与 C++,是使 C++ 这样一个功能强大和表达语言。

因此本月我将介绍一些 C++ 语言,Visual C++ 已添加作为即将发布的 C 的一部分,添加 ++ 0 x 标准。 我将介绍在将 并行模式库 (PPL) Microsoft 已经开发了通过 C 上面和 ++ 0 x 标准引入并行性应用程序以自然补充标准 C++ 库的方式。

语言增强功能

可能 2008 文章"中 C++ Plus: Beef Up Windows Visual C++ 2008 功能包的应用程序"我最初引入的 Visual C++ 2008 功能包,现在技术报告 1 (TR1) 的一部分包括与 Visual Studio 2008 SP 1 引入了标准 C++ 库将添加。 本文,我演示了支持通过函数模板类和绑定模板函数的函数对象。 能够处理函数 polymorphically 解决许多 C++ 开发人员经常遇到编写,或使用泛型算法时在合适的问题。

作为一个的 recap 此处是您初始化标准以及算法与函数对象的方式的示例:

function<int (int, int)> f = plus<int>();
int result = f(4, 5);

绑定函数的借助您可以转换提供所需功能,但很不具有正确的签名的函数。

在以下示例,我正在初始化成员函数的函数对象包含占位符使用绑定函数:

struct Adder
{
   int Add(int x, int y, void* /*reserved*/)
   {
       return x + y;
   }
};

Adder adder;
function<int (int, int)> f = bind(&Adder::Add, &adder, _1, _2, 
    static_cast<void*>(0));
int result = f(4, 5);

有可能出现这些库添加,无法轻松地克服没有语言增强功能的使用的两个问题。 对于初学者来说很通常效率很低显式定义一个函数对象为它添加某些开销,否则必须避免编译器。 也可以是完全冗余并乏味到编译器清楚地知道该签名时 re-declare 函数原型的最佳匹配初始化表达式。

这是新的自动关键字的帮助您。 可以用而不用的显式定义一个变量模板 metaprogramming 中很有用的类型的特定类型是难定义或复杂到 Express。 此处的如其下所示:

auto f = plus<int>();

函数本身的定义还可以受益于一些改进。 通常可以重复使用很有帮助的算法例如标准 C++ 库中。 多个不,但是,您需要编写一些特定于域的函数为非常特定目的不是通常可重复使用。

但由于该函数必须其他地方定义,您必须考虑逻辑和物理设计。 岂不是很好未能定义放置在最需要它的位置,请通过提高逻辑的位置,并改进整体设计封装中简化代码了解? lambda 表达式的允许完全的:

auto f = [](int x, int y) { return x + y; };

lambda 表达式定义有时称为闭包的未命名的函数对象。 在 [] 是通知编译器开始 lambda 表达式的提示。 这称为该 lambda introducer 后, 跟参数列表。 虽然编译器将可以在以前的代码段中那样明确,推断类型时通常省略此参数声明还可以包含返回的类型。 实际上,如果该函数不接受任何参数,可以省略参数列表本身。 lambda 表达式的结果任意数量的大括号内的 C++ 语句。

lambda 表达式还可以捕获用于该函数从在其中定义 lambda 表达式的作用域中的变量。 以下是使用 lambda 表达式计算在容器中值的总和的一个示例:

int sum = 0;
for_each(values.begin(), values.end(), [&sum](int value)
{
    sum += value;
});

授予,这可能已被执行更简洁,Accumulate 函数,但是,将会丢失点。 本示例演示 sum 变量的捕获通过引用和函数中使用的方式。

并行算法

在 PPL 引入了一面向任务的并行性结构,以及许多并行算法与 OpenMP 今天的可用组。 PPL 算法,但是,编写使用 C++ 模板,而不是编译指令,并因此,是更加表达和灵活。 但是,此 PPL 是根本不同于 OpenMP 的在 PPL 提升一组基元和更加可组合和作为模式的一组可重用的算法。 同时,OpenMP 本质上更声明性且明确如计划的问题中,并且最终不正确的 C++ 的一部分。 在 PPL 是也之上并发运行库使用基于相同的运行库的其他库允许更大可能的互操作。 让我们看 PPL 算法,,看看如何直接为面向任务的并行性使用所需的基本功能。

在本专栏的 10 月 2008 期 (" 研究高性能算法"),我演示了有效的算法的优点以及位置和对性能的缓存关心设计后的效果。 我显示如何效率很低,单线程算法用于将一个大图像转换为灰度时间 46 秒,而仍然只使用单个线程的有效实现所需的只是 2 秒。 与 OpenMP I 的一个小 sprinkling 无法 Y 轴上并行算法,并减少进一步的时间。 图 1 显示为 OpenMP 算法代码。

图 1 灰度算法使用 OpenMP

struct Pixel
{
    BYTE Blue;
    BYTE Green;
    BYTE Red;
    BYTE Alpha;
};

void MakeGrayscale(Pixel& pixel)
{
    const BYTE scale = static_cast<BYTE>(0.30 * pixel.Red +
                                         0.59 * pixel.Green +
                                         0.11 * pixel.Blue);

    pixel.Red = scale;
    pixel.Green = scale;
    pixel.Blue = scale;
}

void MakeGrayscale(BYTE* bitmap,
                   const int width,
                   const int height,
                   const int stride)
{
    #pragma omp parallel for
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            const int offset = x * sizeof(Pixel) + y * stride;

            Pixel& pixel = *reinterpret_cast<Pixel*>(bitmap + offset);

            MakeGrayscale(pixel);
        }
    }
}

在 PPL 包括可以非常自然,与 lambda 表达式中的一些帮助,替换 OpenMP 从 MakeGrayscale 函数中的使用的算法的并行 图 1 :

parallel_for(0, height, [&] (int y)
{
    for (int x = 0; x < width; ++x)
    {
        // omitted for brevity
    }
});

如您所见,为循环以及 OpenMP 杂注已替换 parallel_for 函数。 函数的前两个参数定义迭代的范围,类似的前一个 for 循环。 与 OpenMP,不同的置于很大的限制在指令,parallel_for 是模板函数使您可以例如,循环无符号的类型或从标准容器更复杂的迭代器。 最后一个参数是一个我为 lambda 表达式定义的函数对象。

您会注意 lambda introducer 包括仅与号没有显式声明要捕获的任何变量。 这将告知编译器通过引用捕获所有可能的变量。 因为 lambda 表达式中的语句中使用变量许多,我使用这作为一种简写形式。 小心,但是,因为编译器无法优化掉所有不使用的变量,导致较差的运行时性能。 我可以具有显式捕获的变量我需要使用以下捕获列表:

 [&bitmap, width, stride]

正如在 parallel_for 一样函数并且并行除了在 For 循环中,提供的索引范围内的并行迭代在 PPL 还提供了 parallel_for_each 模板函数作为标准 for_each 函数并行的替代方案。 它通过一对迭代器如提供由标准容器定义的元素的范围内提供并行迭代。 尽管它由前面的示例使用显式索引 parallel_for 函数的更多适合,通常很多自然使用迭代器定义的元素的范围。 给定号的数组,未能方形及其值如下所示使用 parallel_for 函数:

array<int, 5> values = { 1, 2, 3, 4, 5 };

parallel_for(0U, values.size(), [&values] (size_t i)
{
    values[i] *= 2;
});

但这种方法是过于详细,需要将数组本身被捕获,lambda 表达式,并根据种容器类型,可能效率很低。 parallel_for_each 函数很好地解决这些问题:

parallel_for_each(values.begin(), values.end(), [] (int& value)
{
    value *= 2;
});

如果只想在并行,运行在多个函数或一样并行是可能基于可用的硬件线程数中,您可以使用 parallel_invoke 模板函数。 有可接受任意位置从 2 到 10 个函数对象的重载。 以下是并行运行 3 lambda 函数的一个示例:

combinable<int> sum;

parallel_invoke([&] { sum.local() += 1; },
                [&] { sum.local() += 2; },
                [&] { sum.local() += 3; });

int result = sum.combine([] (int left, int right)
{
    return left + right;
});

ASSERT(6 == result);

此示例还演示了在 PPL 所提供的另一个帮助器类。 combinable 类非常可以方便地组合的最少的锁定的许多并行任务的结果。 通过为每个线程提供值的本地副本并并行工作已结束后只合并每个线程的结果,combinable 类避免了大量通常发生在这种情况下锁定。

任务和任务组

实际并行性,我讨论了算法中的一个简单的面向任务的 API,您都能够直接使用,可以实现。 任务定义与 task_handle 类初始化函数对象。 任务进行分组以及与运行任务,并等待其完成 task_group 类。 当然,在 task_group 提供有用的重载,以便在许多情况下甚至不必自己定义 task_handle 对象和可以 task_group 对象分配和管理其生存期。 以下是如何将 parallel_invoke 函数从上一个示例使用一个 task_group 的一个示例:

task_group group;
group.run([&] { sum.local() += 1; });
group.run([&] { sum.local() += 2; });
group.run([&] { sum.local() += 3; });
group.wait();

除了算法和我此处讨论的 API 中,其他并行算法和帮助器类也可能包含与 Visual C++ 2010 最后释放并行模式库时。 若要保持在最新并发操作中,请访问 并行本机代码中的编程.

将您的问题和提出的意见发送至 mmwincpp@Microsoft.com.

通过 Kerr 是一个软件 craftsman 擅长用于 Windows 软件开发中。 他热衷一个用于编写和向有关编程和软件设计的开发人员讲授。 访问在通过 weblogs.asp。 net / kennykerr.