我执行了 3 次 QuickSort-Algorithm 并测量了对 5000 万个随机数进行排序的时间:
顺序(大约需要 14 秒)
使用 Parallel.Invoke() 作为排序算法的相同方法(耗时约 12 秒)
使用 Parallel.Invoke() 在单独的方法中(耗时约 7 秒)
所以我的问题是:如果在单独的方法中调用,为什么 Parallel.Invoke() 会快得多?
在我的电脑上,示例 3. 的速度是示例 2 的两倍多。
Parallel.Invoke() 作为排序算法的相同方法public class ParallelQuickSort
{
private const int Threshold = 100;
public static void Sort(int[] array)
{
if (array == null || array.Length == 0)
{
new ArgumentException("number array must be at least of length 1");
}
QuickSort(array, 0, array.Length - 1);
}
private static void QuickSort(int[] array, int left, int right)
{
var i = left;
var j = right;
var m = array[(left + right) / 2];
while (i <= j)
{
while (array[i] < m) { i++; }
while (array[j] > m) { j--; }
if (i <= j)
{
var t = array[i]; array[i] = array[j]; array[j] = t;
i++; j--;
}
}
if (j - left > Threshold && right - i > Threshold)
{
Parallel.Invoke(
() => QuickSort(array, left, j),
() => QuickSort(array, i, right)
);
}
else
{
if (j > left) { QuickSort(array, left, j); }
if (i < right) { QuickSort(array, i, right); }
}
}
}
Parallel.Invoke()
public class ParallelSeparateMethodQuickSort
{
private const int Threshold = 100;
public static void Sort(int[] array)
{
if (array == null || array.Length == 0)
{
new ArgumentException("number array must be at least of length 1");
}
QuickSort(array, 0, array.Length - 1);
}
private static void QuickSort(int[] array, int left, int right)
{
var i = left;
var j = right;
var m = array[(left + right) / 2];
while (i <= j)
{
while (array[i] < m) { i++; }
while (array[j] > m) { j--; }
if (i <= j)
{
var t = array[i]; array[i] = array[j]; array[j] = t;
i++; j--;
}
}
if (j - left > Threshold && right - i > Threshold)
{
ParallelInvoke(array, left, j, i, right);
}
else
{
if (j > left) { QuickSort(array, left, j); }
if (i < right) { QuickSort(array, i, right); }
}
}
private static void ParallelInvoke(int[] array, int left, int j, int i, int right)
{
Parallel.Invoke(
() => QuickSort(array, left, j),
() => QuickSort(array, i, right)
);
}
}
using System;
using System.Threading.Tasks;
using System.Diagnostics;
namespace parallelQuicksort
{
class Program
{
static void Main(string[] args)
{
const int N = 50_000_000;
for (int i = 0; i < 5; i++)
{
var array = GetRandomArray(N);
Measure("Sequential\t", () => SequentialQuickSort.Sort(array));
array = GetRandomArray(N);
Measure("Parallel\t", () => ParallelQuickSort.Sort(array));
array = GetRandomArray(N);
Measure("P. Separate Method", () => ParallelSeparateMethodQuickSort.Sort(array));
}
}
private static int[] GetRandomArray(int length)
{
var random = new Random(4711);
var array = new int[length];
for (int i = 0; i < length; i++)
{
array[i] = random.Next();
}
return array;
}
public static void Measure(string name, Action action)
{
Stopwatch stopwatch = Stopwatch.StartNew();
action();
stopwatch.Stop();
var time = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"{name}: \tElapsed={time}");
}
}
public class SequentialQuickSort
{
public static void Sort(int[] array)
{
if (array == null || array.Length == 0)
{
new ArgumentException("number array must be at least of length 1");
}
QuickSort(array, 0, array.Length - 1);
}
private static void QuickSort(int[] array, int left, int right)
{
var i = left;
var j = right;
var m = array[(left + right) / 2];
while (i <= j)
{
while (array[i] < m) { i++; }
while (array[j] > m) { j--; }
if (i <= j)
{
var t = array[i]; array[i] = array[j]; array[j] = t;
i++; j--;
}
}
if (j > left) { QuickSort(array, left, j); }
if (i < right) { QuickSort(array, i, right); }
}
}
public class ParallelQuickSort
{
private const int Threshold = 100;
public static void Sort(int[] array)
{
if (array == null || array.Length == 0)
{
new ArgumentException("number array must be at least of length 1");
}
QuickSort(array, 0, array.Length - 1);
}
private static void QuickSort(int[] array, int left, int right)
{
var i = left;
var j = right;
var m = array[(left + right) / 2];
while (i <= j)
{
while (array[i] < m) { i++; }
while (array[j] > m) { j--; }
if (i <= j)
{
var t = array[i]; array[i] = array[j]; array[j] = t;
i++; j--;
}
}
if (j - left > Threshold && right - i > Threshold)
{
Parallel.Invoke(
() => QuickSort(array, left, j),
() => QuickSort(array, i, right)
);
}
else
{
if (j > left) { QuickSort(array, left, j); }
if (i < right) { QuickSort(array, i, right); }
}
}
}
public class ParallelSeparateMethodQuickSort
{
private const int Threshold = 100;
public static void Sort(int[] array)
{
if (array == null || array.Length == 0)
{
new ArgumentException("number array must be at least of length 1");
}
QuickSort(array, 0, array.Length - 1);
}
private static void QuickSort(int[] array, int left, int right)
{
var i = left;
var j = right;
var m = array[(left + right) / 2];
while (i <= j)
{
while (array[i] < m) { i++; }
while (array[j] > m) { j--; }
if (i <= j)
{
var t = array[i]; array[i] = array[j]; array[j] = t;
i++; j--;
}
}
if (j - left > Threshold && right - i > Threshold)
{
ParallelInvoke(array, left, j, i, right);
}
else
{
if (j > left) { QuickSort(array, left, j); }
if (i < right) { QuickSort(array, i, right); }
}
}
private static void ParallelInvoke(int[] array, int left, int j, int i, int right)
{
Parallel.Invoke(
() => QuickSort(array, left, j),
() => QuickSort(array, i, right)
);
}
}
}
您可以在这里找到我的代码:https://github.com/Lazzaretti/ParallelQuicksort
Sequential : Elapsed=14534
Parallel : Elapsed=11960
P. Separate Method: Elapsed=6353
Sequential : Elapsed=14620
Parallel : Elapsed=11954
P. Separate Method: Elapsed=6647
Sequential : Elapsed=14529
Parallel : Elapsed=11870
P. Separate Method: Elapsed=6389
Sequential : Elapsed=14512
Parallel : Elapsed=11787
P. Separate Method: Elapsed=6590
Sequential : Elapsed=16203
Parallel : Elapsed=11738
P. Separate Method: Elapsed=6674
最佳答案
解决了对评论中提到的已排序数组进行排序的问题后,您的问题仍然重现。
我认为原因是您传递给 Parallel.Invoke 的 lambda 捕获的内容和方式。
第一种情况(当 Parallel.Invoke 位于 QuickSort 方法中时):
Parallel.Invoke(
() => QuickSort(array, left, j),
() => QuickSort(array, i, right)
);
您捕获了 5 个变量,所有这些变量都用于整个 QuickSort 方法。所有这些变量都成为编译器生成类的字段。这意味着整个 QuickSort 方法现在适用于对象字段而不是局部变量。如果你反编译那个方法,你会看到类似这样的东西:
var cDisplayClass20 = new SomeCompilerGeneratedClass();
cDisplayClass20.array = array;
cDisplayClass20.left = left;
cDisplayClass20.right = right;
cDisplayClass20.i = cDisplayClass20.left;
cDisplayClass20.j = cDisplayClass20.right;
int num1 = cDisplayClass20.array[(cDisplayClass20.left + cDisplayClass20.right) / 2];
while (cDisplayClass20.i <= cDisplayClass20.j) // field access
{
while (cDisplayClass20.array[cDisplayClass20.i] < num1) // field access
cDisplayClass20.i++;
while (cDisplayClass20.array[cDisplayClass20.j] > num1) // and again
cDisplayClass20.j--;
if (cDisplayClass20.i <= cDisplayClass20.j) // again field access
{
// they are everywhere
int num2 = cDisplayClass20.array[cDisplayClass20.i];
cDisplayClass20.array[cDisplayClass20.i] = cDisplayClass20.array[cDisplayClass20.j];
cDisplayClass20.array[cDisplayClass20.j] = num2;
cDisplayClass20.i++;
cDisplayClass20.j--;
}
}
这证实了上面的观点。
但是,如果您将 Parallel.Invoke 移动到单独的方法中,情况就不再是这样了。仍然捕获了 5 个变量,但这并不影响整个 QuickSort 方法,因为捕获现在发生在单独的 ParallelInvoke 方法中,因此是本地化的。 QuickSort 仍然适用于局部变量,而不适用于编译器生成类的字段。如果你用单独的方法反编译版本,它看起来就像你写的一样:
int index1 = left;
int index2 = right;
int num1 = array[(left + right) / 2];
while (index1 <= index2) // local variables
{
while (array[index1] < num1) // num1 is local variable
++index1;
while (array[index2] > num1)
--index2;
if (index1 <= index2) // local variables again
{
int num2 = array[index1];
array[index1] = array[index2];
array[index2] = num2;
++index1;
--index2;
}
}
...
现在,我假设访问对象字段(通常在堆上)比访问局部变量(通常在堆栈上\在 CPU 寄存器中)要慢一些,因此具有单独方法的版本更快。 Eric Lippert 还在评论中指出:
The jitter will likely do a worse job with the fields than it will with the locals because it will not enregister them as aggressively.
您可以像这样修改您的第一个版本来确认以上内容:
private static void QuickSort(int[] array, int left, int right) {
var i = left;
var j = right;
var m = array[(left + right) / 2];
while (i <= j) {
while (array[i] < m) {
i++;
}
while (array[j] > m) {
j--;
}
if (i <= j) {
var t = array[i];
array[i] = array[j];
array[j] = t;
i++;
j--;
}
}
if (j - left > Threshold && right - i > Threshold) {
// copy all variables you pass to lambda
// so that their capture does not affect the whole method
var tmpArray = array;
var tmpLeft = left;
var tmpJ = j;
var tmpI = i;
var tmpRight = right;
Parallel.Invoke(
() => QuickSort(tmpArray, tmpLeft, tmpJ),
() => QuickSort(tmpArray, tmpI, tmpRight)
);
}
else {
if (j > left) {
QuickSort(array, left, j);
}
if (i < right) {
QuickSort(array, i, right);
}
}
}
}
然后两种方法的执行时间将相同。
正如@Eugene 在评论和他的回答中提到的那样 - 除了字段与局部变量访问之外,可能还有其他事情会减慢速度 - 例如上述编译器生成的类的构造和(可能)垃圾收集。然而,这些都是同源根的结果——以不同方式在闭包中捕获局部变量。
关于c# - 如果在单独的方法中调用,为什么 Parallel.Invoke 会快得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49818054/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val