我正在做一个简单的作业,其中我必须用C语言开发一个软件才能找到其中100个之间最接近的两个点。
当我完成时,我很想知道运行更多的点并启用完整的VC++优化将花费多少时间。我尝试使用10000,大约花了8〜9秒。然后我很好奇,看看C#和Java需要花费多少时间来完成相同的事情。正如预期的那样,C#花费了更长的时间9〜10秒;但是,Java仅花费了约400毫秒!为什么会发生这种情况?
这是我在C,C#和Java中的代码:
C:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <Windows.h>
long perfFrequency = 0;
typedef struct
{
double X;
double Y;
} Point;
double distance(Point p1, Point p2)
{
return sqrt(pow(p1.X - p2.X, 2) + pow(p1.Y - p2.Y, 2));
}
double smallerDistance(Point *points, int size, Point *smallerA, Point *smallerB)
{
int i, j;
double smaller = distance(points[0], points[1]);
for (i = 0; i < size; i++)
{
for (j = i + 1; j < size; j++)
{
double dist = distance(points[i], points[j]);
if (dist < smaller)
{
smaller= dist;
*smallerA = points[i];
*smallerB = points[j];
}
}
}
return smaller;
}
void main()
{
// read size and points from file.
int size;
Point *points= (Point *)malloc(size * sizeof(Point));
// just to make sure everything is ready before the benchmark begins
system("pause");
Point smallerA, smallerB;
if (!QueryPerformanceFrequency((LARGE_INTEGER *)&perfFrequency))
printf("Couldn't query performance frequency.");
long long start, end;
double smaller;
QueryPerformanceCounter((LARGE_INTEGER *)&start);
smaller= smallerDistance(points, size, &smallerA, &smallerB);
QueryPerformanceCounter((LARGE_INTEGER *)&end);
printf("The smaller distance is: %lf. The coordinates of the most close points are: (%lf, %lf) and (%lf, %lf). Time taken: %lfms\n",
smaller, smallerA.X, smallerA.Y, smallerB.X, smallerB.Y, (end - start) * 1000.0 / perfFrequency);
}
C#:
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace StructuredTest
{
struct Point
{
public double X;
public double Y;
}
class Program
{
static double Distance(Point p1, Point p2)
{
return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
}
static double SmallerDistance(Point[] points, int size, out Point smallerA, out Point smallerB)
{
int i, j;
double smaller = Distance(points[0], points[1]);
smallerA = default(Point);
smallerB = default(Point);
for (i = 0; i < size; i++)
{
for (j = i + 1; j < size; j++)
{
double dist = Distance(points[i], points[j]);
if (dist < smaller)
{
smaller = dist;
smallerA = points[i];
smallerB = points[j];
}
}
}
return smaller;
}
static void Main(string[] args)
{
// read size and points from file
int size = int.Parse(file[0]);
Point[] points= new Point[size];
// make sure everything is ready
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
Point smallerA, smallerB;
double smaller;
Stopwatch sw = new Stopwatch();
sw.Restart();
smaller = SmallerDistance(points, size, out smallerA, out smallerB);
sw.Stop();
Console.WriteLine($"The smaller distance is: {smaller}. The coordinates of the most close points are: ({smallerA.X}, {smallerA.Y}) and " +
$"({smallerB.X}, {smallerB.Y}). Time taken: {sw.ElapsedMilliseconds}ms.");
}
}
}
Java的:
package structuredtest;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
class Point {
public Point(double X, double Y) {
this.X = X;
this.Y = Y;
}
double X;
double Y;
}
class Result {
double distance;
Point p1;
Point p2;
}
public class StructuredTest {
static double distance(Point p1, Point p2) {
return Math.sqrt(Math.pow(p1.X - p2.X, 2) + Math.pow(p1.Y - p2.Y, 2));
}
static Result smallerDistance(Point[] points, int size) {
int i, j;
double smaller = distance(points[0], points[1]);
Result r = new Result();
for (i = 0; i < size; i++) {
for (j = i + 1; j < size; j++) {
double dist = distance(points[i], points[j]);
if (dist < smaller) {
smaller = dist;
r.p1 = points[i];
r.p2 = points[j];
}
}
}
r.distance = smaller;
return r;
}
public static void main(String[] args) throws IOException {
// read size and points from file
int size = Integer.parseInt(file[0]);
Point[] points = new Point[size];
// make sure everything is ready
System.out.println("Press any key to continue...");
System.in.read();
double start = System.nanoTime(), end;
Result r = smallerDistance(points, size);
end = System.nanoTime();
System.out.println("The smaller distance is: " + r.distance + ". The most close points are: ("
+ r.p1.X + "," + r.p1.Y + ") and " + r.p2.X + "," + r.p2.Y + "). Time taken: " + (end - start) / 1000000 + "ms.");
}
}
如果java在C和C#方面都取得了微弱的成绩,我不会感到惊讶,但是会快20倍吗?
该文件的格式如下:
3 // number of points in the file. Note that there no comments in the actual file
(3.7098722472288, 4.49056397953787) // point (X,Y)
(8.90232811621332, 9.67982769279173)
(5.68254334818822, 1.71918922506136)
(6.22585901842366, 9.51660500242835)
有趣的是:起初,我之前提到的用于基准测试的具有10000点的文件实际上只是另一个具有100个随机点的文件的复制粘贴的100倍。像这样:
(Point 1)
(Point 2)
(Point 3)
(Point 1)
(Point 2)
(Point 3)
我认为不需要生成10000个随机点,因为因为无论如何代码都必须遍历所有数字,所以差别不大(只有更多分配)。但是后来我决定生成10000个随机点,以查看它们的 react :C和C#仍在大约相同的时间内运行(增加了50毫秒)。另一方面,Java增加了〜500ms。
另外,我认为值得注意的是,在NetBeans中运行Java大约需要11秒(即使是在“运行”模式下,而不是在“调试”模式下)。
而且我还尝试了使用C++而不是C进行编译,但这没什么区别。
我正在将VS 2015用于C和C#。
这些是每种语言的设置:
C:
x64
Optimization: Maximize Speed (/O2)
Intrinsic Functions: Yes (/Oi)
Favor Size or Speed: Favor fast code (/Ot)
Enable Fiber-Safe Optimizations: Yes (/GT)
Security Check: Disable Security Check (/GS-)
Floating point model: Fast (/fp:fast)
Everything else: default
C#:
x64
Release Mode
Optimize Code: Enabled
Check for arithmetic overflow: Disabled
.NET 4.5.2
Java的:
JRE/JDK 1.8
Default settings (if there are any)
编辑:
好的,我按照建议重新进行了测试:
首先,我在C和C#中都使用了
Result
类/结构。我在Java中而不是在C/C#中使用它的原因是因为Java无法通过引用传递。其次,我现在在main()函数中重复测试。
并感谢@Tony D捕获了该错误! :)
我不会发布代码,因为更改很小:只需在其他测试中完全实现Java版本,这就是我所做的。
这次,我仅用7000点(而不是10000)进行了测试,并且仅进行了30次迭代,因为测试时间很长,而且到现在为止还很晚。
结果变化不大:C#平均花费5228毫秒,C花费4424毫秒,Java花费223毫秒。 Java仍能以快20倍或更多的速度获胜。
然后,我尝试删除对Math.Pow的调用(只需更改
((p1.X - p2.X) * (p1.X - p2.X)) + ((p1.Y - p2.Y) * (p1.Y - p2.Y))
),然后一切都变了。新结果:Java:平均220毫秒
C#:平均195ms
C:平均195ms
如果我只在:p之前检查过
正如我评论过的那样,我曾考虑过这样做,但后来决定最好测试每个编译器内联函数并优化这种简单调用的能力。但是,当我获得那些奇怪的结果时,我应该回去做,但是我是如此的紧张以至于我忘了这样做。
无论如何,说实话,我很惊讶Java编译器能够完全优化该行代码,而C#和C++却没有。尽管我知道极端情况检查和对C#的内部调用,但我发现Java编译器能够注意到该代码中不需要进行极端情况检查确实使我感到非常有趣。
最佳答案
如here和checked here所述,在纯C语言中,没有重载,具有整数幂,如下所示:
double pow(double base, int exponent );
这意味着,当您在C语言中调用
pow
时,其处理方式类似于:double pow(double base, double exponent) {
return exp(log(base) * exponent);
}
此外,还应对以负幂和整数幂的情况进行一些检查,以特殊方式进行处理。在此处添加诸如
if (exponent == 1.0)
和if (exponent == 2.0)
之类的条件不是一个好主意,因为这会减慢真正将pow
用于适当目的的数学代码。结果,平方变得更慢in twelve times or something like that。原则上,将
pow(x, 2)
优化为x * x
的唯一合理方法是使编译器识别此类反模式并为其生成特殊代码。发生这种情况的原因是,具有您的设置的C和C#编译器无法做到,而Java编译器却可以做到。请注意,它与内联功能无关:仅内联exp(log(a) * b)
不会使此代码更快。最后,我想指出的是,没有一个擅长编写数学代码的程序员会在他的代码中编写
pow(x, 2)
。