Helpex - Trao đổi & giúp đỡ Đăng nhập
34

Hôm nay tôi đang làm một dự án và thấy mình đang sử dụng Math.Max ​​ở một số nơi và các câu lệnh if nội tuyến ở những nơi khác. Vì vậy, tôi đã tự hỏi liệu có ai biết cái nào "tốt hơn" ... hay đúng hơn, sự khác biệt thực sự là gì.

Ví dụ, trong phần sau c1 = c2:

Random rand = new Random();
int a = rand.next(0,10000);
int b = rand.next(0,10000);

int c1 = Math.Max(a, b);
int c2 = a>b ? a : b;

Tôi đang hỏi cụ thể về C #, nhưng tôi cho rằng câu trả lời có thể khác nhau ở các ngôn ngữ khác nhau, mặc dù tôi không chắc ngôn ngữ nào có khái niệm tương tự.

34 hữu ích 5 bình luận 14k xem chia sẻ
33

Một trong những điểm khác biệt chính mà tôi sẽ nhận thấy ngay lập tức là vì lợi ích dễ đọc, theo như tôi biết vì lợi ích triển khai / hiệu suất, chúng sẽ gần như tương đương.

Math.Max(a,b) rất đơn giản để hiểu, bất kể kiến ​​thức mã hóa trước đó.

a>b ? a : b ít nhất sẽ yêu cầu người dùng có một số kiến ​​thức về toán tử bậc ba.

" Khi nghi ngờ - hãy tìm kiếm khả năng đọc "

33 hữu ích 5 bình luận chia sẻ
27

Tôi nghĩ rằng sẽ rất thú vị khi đưa một số con số vào cuộc thảo luận này vì vậy tôi đã viết một số mã để làm hồ sơ cho nó. Đúng như dự đoán, chúng gần như giống hệt nhau cho tất cả các mục đích thực tế.

Mã thực hiện một tỷ vòng lặp (yep 1 tỷ). Trừ chi phí của vòng lặp bạn nhận được:

  • Math.Max ​​() mất 0,0044 giây để chạy 1 tỷ lần
  • Nội tuyến nếu mất 0,0055 giây để chạy 1 tỷ lần

Tôi đã trừ chi phí mà tôi đã tính toán bằng cách chạy một vòng lặp trống 1 tỷ lần, tổng chi phí là 1,2 giây.

Tôi đã chạy điều này trên máy tính xách tay, Windows 7 64-bit, Intel Core i5 (U470) 1,3 Ghz. Mã được biên dịch ở chế độ phát hành và chạy mà không có trình gỡ lỗi đính kèm.

Đây là mã:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TestMathMax {
    class Program {
        static int Main(string[] args) {
            var num1 = 10;
            var num2 = 100;
            var maxValue = 0;
            var LoopCount = 1000000000;
            double controlTotalSeconds;
            { 
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (var i = 0; i < LoopCount; i++) {
                    // do nothing
                }
                stopwatch.Stop();
                controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
                Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = Math.Max(num1, num2);
                }
                stopwatch.Stop();
                Console.WriteLine("Math.Max() - " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = num1 > num2 ? num1 : num2;
                }
                stopwatch.Stop();
                Console.WriteLine("Inline Max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }

            Console.ReadLine();

            return maxValue;
        }
    }
}

CẬP NHẬT Kết quả 2/7/2015

Trên Windows 8.1, Surface 3 Pro, i7 4650U 2.3Ghz Ran dưới dạng ứng dụng bảng điều khiển ở chế độ phát hành mà không có trình gỡ lỗi đính kèm.

  • Math.Max ​​() - 0,3194749 giây
  • Inline Max: 0,3465041 giây
27 hữu ích 4 bình luận chia sẻ
16

nếu tuyên bố được coi là có lợi

Tóm lược

một câu lệnh có dạng if (a > max) max = alà cách nhanh nhất để xác định giá trị lớn nhất của một tập hợp số. Tuy nhiên, bản thân cơ sở hạ tầng vòng lặp chiếm phần lớn thời gian của CPU, vì vậy cuối cùng việc tối ưu hóa này vẫn còn nhiều nghi vấn.

Chi tiết

Câu trả lời của luisperezphd rất thú vị vì nó cung cấp các con số, tuy nhiên tôi tin rằng phương pháp này còn thiếu sót: trình biên dịch rất có thể sẽ di chuyển so sánh ra khỏi vòng lặp, vì vậy câu trả lời không đo lường những gì nó muốn đo lường. Điều này giải thích sự khác biệt về thời gian không đáng kể giữa vòng điều khiển và vòng đo.

Để tránh tối ưu hóa vòng lặp này, tôi đã thêm một thao tác phụ thuộc vào biến vòng lặp, vào vòng điều khiển trống cũng như tất cả các vòng đo. Tôi mô phỏng trường hợp sử dụng phổ biến là tìm số tối đa trong danh sách số và sử dụng ba tập dữ liệu:

  • trường hợp tốt nhất: số đầu tiên là số lớn nhất, tất cả các số sau nó đều nhỏ hơn
  • trường hợp xấu nhất: mọi số đều lớn hơn số trước, vì vậy giá trị tối đa thay đổi mỗi lần lặp
  • trường hợp trung bình: một tập hợp các số ngẫu nhiên

Xem bên dưới để biết mã.

Kết quả là khá bất ngờ đối với tôi. Trên máy tính xách tay Core i5 2520M của tôi, tôi nhận được thông tin sau cho 1 tỷ lần lặp (điều khiển trống mất khoảng 2,6 giây trong mọi trường hợp):

  • max = Math.Max(max, a): Trường hợp tốt nhất 2,0 giây / trường hợp xấu nhất 1,3 giây / trường hợp trung bình 2,0 giây
  • max = Math.Max(a, max): Trường hợp tốt nhất 1,6 giây / trường hợp xấu nhất 2,0 giây / trường hợp trung bình 1,5 giây
  • max = max > a ? max : a: Trường hợp tốt nhất 1,2 giây / trường hợp xấu nhất 1,2 giây / trường hợp trung bình 1,2 giây
  • if (a > max) max = a: Trường hợp tốt nhất 0,2 giây / trường hợp xấu nhất 0,9 giây / trường hợp trung bình 0,3 giây

Vì vậy, mặc dù các đường ống CPU dài và các hình phạt dẫn đến việc phân nhánh, ifcâu lệnh cũ tốt là người chiến thắng rõ ràng cho tất cả các tập dữ liệu mô phỏng; trong trường hợp tốt nhất, nó nhanh hơn 10 lần Math.Maxvà trong trường hợp xấu nhất vẫn nhanh hơn 30%.

Một điều ngạc nhiên khác là thứ tự của các lập luận đối với Math.Maxcác vấn đề. Có lẽ điều này là do logic dự đoán nhánh CPU hoạt động khác nhau trong hai trường hợp và việc dự đoán sai các nhánh nhiều hay ít tùy thuộc vào thứ tự của các đối số.

Tuy nhiên, phần lớn thời gian của CPU được sử dụng trong cơ sở hạ tầng vòng lặp, vì vậy cuối cùng, việc tối ưu hóa này tốt nhất là có vấn đề. Nó cung cấp một sự giảm thiểu thời gian thực hiện tổng thể có thể đo lường được nhưng giảm nhỏ.

CẬP NHẬT bởi luisperezphd

Tôi không thể phù hợp với điều này như một nhận xét và sẽ có ý nghĩa hơn khi viết nó ở đây thay vì là một phần của câu trả lời của tôi để nó phù hợp với ngữ cảnh.

Lý thuyết của bạn có lý, nhưng tôi không thể tái tạo kết quả. Đầu tiên vì một số lý do sử dụng mã của bạn, vòng điều khiển của tôi mất nhiều thời gian hơn các vòng chứa công việc.

Vì lý do đó, tôi đã thực hiện các con số ở đây tương đối với thời gian thấp nhất thay vì vòng điều khiển. Các giây trong kết quả dài hơn bao nhiêu so với thời gian nhanh nhất. Ví dụ: trong các kết quả ngay bên dưới, thời gian nhanh nhất là trường hợp tốt nhất của Math.Max ​​(a, max), vì vậy mọi kết quả khác thể hiện thời gian chúng mất bao lâu hơn thời gian đó.

Dưới đây là kết quả tôi nhận được:

  • max = Math.Max(max, a): Trường hợp tốt nhất 0,012 giây / trường hợp xấu nhất 0,007 giây / trường hợp trung bình 0,028 giây
  • max = Math.Max(a, max): 0,000 trường hợp tốt nhất / 0,021 trường hợp xấu nhất / trường hợp trung bình 0,019 giây
  • max = max > a ? max : a: Trường hợp tốt nhất 0,022 giây / Trường hợp xấu nhất 0,02 giây / Trường hợp trung bình 0,01 giây
  • if (a > max) max = a: Trường hợp tốt nhất 0,015 giây / Trường hợp xấu nhất 0,024 giây / Trường hợp trung bình 0,019 giây

Lần thứ hai tôi chạy nó, tôi nhận được:

  • max = Math.Max(max, a): Trường hợp tốt nhất 0,024 giây / Trường hợp xấu nhất 0,010 giây / Trường hợp trung bình 0,009 giây
  • max = Math.Max(a, max): Trường hợp tốt nhất 0,001 giây / trường hợp xấu nhất 0,000 giây / trường hợp trung bình 0,018 giây
  • max = max > a ? max : a: Trường hợp tốt nhất 0,011 giây / Trường hợp xấu nhất 0,005 giây / Trường hợp trung bình 0,018 giây
  • if (a > max) max = a: Trường hợp tốt nhất 0,000 giây / trường hợp xấu nhất 0,005 giây / trường hợp trung bình 0,039 giây

Có đủ khối lượng trong các bài kiểm tra này để xóa sạch mọi dị thường. Tuy nhiên, mặc dù vậy, kết quả khá khác nhau. Có thể việc phân bổ bộ nhớ lớn cho mảng có liên quan đến nó. Hoặc có thể sự khác biệt quá nhỏ đến nỗi bất kỳ điều gì khác xảy ra trên máy tính vào thời điểm đó là nguyên nhân thực sự của sự thay đổi.

Lưu ý rằng thời gian nhanh nhất, được biểu thị trong kết quả ở trên bằng 0,000 là khoảng 8 giây. Vì vậy, nếu bạn xem xét rằng lần chạy dài nhất khi đó là 8.039, thì sự thay đổi về thời gian là khoảng nửa phần trăm (0,5%) - hay còn gọi là quá nhỏ để quan trọng.

Máy tính

Mã được chạy trên Windows 8.1, i7 4810MQ 2.8Ghz và được biên dịch trong .NET 4.0.

Sửa đổi mã

Tôi đã sửa đổi mã của bạn một chút để xuất kết quả ở định dạng được hiển thị ở trên. Tôi cũng đã thêm mã bổ sung để đợi 1 giây sau khi bắt đầu tính cho bất kỳ thời gian tải bổ sung nào .NET có thể cần khi chạy lắp ráp.

Ngoài ra, tôi đã chạy tất cả các bài kiểm tra hai lần để tính đến bất kỳ tối ưu hóa CPU nào. Cuối cùng, tôi đã thay đổi intfor ithành a unitđể tôi có thể chạy vòng lặp 4 tỷ lần thay vì 1 tỷ lần để có khoảng thời gian dài hơn.

Đó có thể là tất cả quá mức cần thiết, nhưng tất cả là để đảm bảo càng nhiều càng tốt rằng các thử nghiệm không bị ảnh hưởng bởi bất kỳ yếu tố nào trong số đó.

Bạn có thể tìm thấy mã tại: http://pastebin.com/84qi2cbD

using System;
using System.Diagnostics;

namespace ProfileMathMax
{
  class Program
  {
    static double controlTotalSeconds;
    const int InnerLoopCount = 100000;
    const int OuterLoopCount = 1000000000 / InnerLoopCount;
    static int[] values = new int[InnerLoopCount];
    static int total = 0;

    static void ProfileBase()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int maxValue;
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                // baseline
                total += values[i];
            }
        }
        stopwatch.Stop();
        controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
        Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
    }

    static void ProfileMathMax()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(values[i], maxValue);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(a, max) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileMathMaxReverse()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(maxValue, values[i]);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(max, a) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileInline()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = maxValue > values[i] ? values[i] : maxValue;
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("max = max > a ? a : max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileIf()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                if (values[i] > maxValue)
                    maxValue = values[i];
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("if (a > max) max = a: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void Main(string[] args)
    {
        Random rnd = new Random();
        for (int i = 0; i < InnerLoopCount; i++)
        {
            //values[i] = i;  // worst case: every new number biggest than the previous
            //values[i] = i == 0 ? 1 : 0;  // best case: first number is the maximum
            values[i] = rnd.Next(int.MaxValue);  // average case: random numbers
        }

        ProfileBase();
        Console.WriteLine();
        ProfileMathMax();
        Console.WriteLine();
        ProfileMathMaxReverse();
        Console.WriteLine();
        ProfileInline();
        Console.WriteLine();
        ProfileIf();
        Console.ReadLine();
    }
  }
}
16 hữu ích 1 bình luận chia sẻ
6

Tôi muốn nói rằng sẽ nhanh hơn để hiểu Math.Max ​​đang làm gì, và đó thực sự nên là yếu tố quyết định duy nhất ở đây.

Nhưng là một niềm đam mê, thật thú vị khi xem xét Math.Max(a,b)đánh giá các đối số một lần, trong khi a > b ? a : bđánh giá một trong số chúng hai lần. Không phải là vấn đề với các biến cục bộ, nhưng đối với các thuộc tính có tác dụng phụ, tác dụng phụ có thể xảy ra hai lần.

6 hữu ích 0 bình luận chia sẻ
6

Nếu JITer chọn nội dòng hàm Math.Max, mã thực thi sẽ giống với câu lệnh if. Nếu Math.Max ​​không được nội tuyến, nó sẽ thực thi dưới dạng một lệnh gọi hàm với lệnh gọi và trả về chi phí không có trong câu lệnh if. Vì vậy, câu lệnh if sẽ cung cấp hiệu suất giống hệt Math.Max ​​() trong trường hợp nội tuyến hoặc câu lệnh if có thể nhanh hơn một vài chu kỳ đồng hồ trong trường hợp không nội dòng, nhưng sự khác biệt sẽ không đáng chú ý trừ khi bạn đang chạy hàng chục trong số hàng triệu phép so sánh.

Vì sự khác biệt về hiệu suất giữa cả hai là đủ nhỏ để không đáng kể trong hầu hết các tình huống, tôi thích Math.Max ​​(a, b) hơn vì nó dễ đọc hơn.

6 hữu ích 4 bình luận chia sẻ
3

Về hiệu suất, CPU hiện đại có đường dẫn lệnh nội bộ để mọi lệnh lắp ráp được thực hiện theo một số bước nội bộ. (ví dụ: tìm nạp, giải thích, tính toán, lưu trữ)

Trong hầu hết các trường hợp, CPU đủ thông minh để chạy song song các bước này cho các lệnh tuần tự nên thông lượng tổng thể rất cao.

Điều này là tốt cho đến khi có một chi nhánh ( if, ?:v.v.). Chi nhánh có thể phá vỡ trình tự và buộc CPU phải dọn rác đường ống. Điều này tốn rất nhiều chu kỳ đồng hồ.

Về lý thuyết, nếu trình biên dịch đủ thông minh, nó Math.Maxcó thể được thực hiện bằng cách sử dụng lệnh CPU đã xây dựng và có thể tránh được việc phân nhánh.

Trong trường hợp Math.Maxnày, thực sự sẽ nhanh hơn if- nhưng nó phụ thuộc vào trình biên dịch ..

Trong trường hợp Max phức tạp hơn - như làm việc trên vectơ, double []v; v.Max()trình biên dịch có thể sử dụng mã thư viện được tối ưu hóa cao, có thể nhanh hơn nhiều so với mã được biên dịch thông thường.

Vì vậy, tốt nhất là bạn nên sử dụng Math.Max, nhưng bạn cũng nên kiểm tra hệ thống mục tiêu và trình biên dịch cụ thể của mình nếu nó đủ quan trọng.

3 hữu ích 0 bình luận chia sẻ
3

Math.Max ​​(a, b)

KHÔNG tương đương để a > b ? a : btrong mọi trường hợp.

Math.Max trả về giá trị lớn hơn của hai đối số, đó là:

if (a == b) return a; // or b, doesn't matter since they're identical
else if (a > b && b < a) return a;
else if (b > a && a < b) return b;
else return undefined;

Không xác định được ánh xạ tới double.NaNtrong trường hợp quá tải kép của Math.Maxví dụ.

a> b? a: b

đánh giá là a nếu a lớn hơn b, điều này không nhất thiết có nghĩa là b nhỏ hơn a.

Một ví dụ đơn giản chứng minh rằng chúng không tương đương:

var a = 0.0/0.0; // or double.NaN
var b = 1.0;
a > b ? a : b // evaluates to 1.0
Math.Max(a, b) // returns double.NaN
3 hữu ích 0 bình luận chia sẻ
0

Thực hiện một hoạt động; N phải> = 0

Các giải pháp chung:

A) N = Math.Max(0, N)
B) if(N < 0){N = 0}

Phân loại theo tốc độ:

Chậm: Math.Max ​​(A) <(B) câu lệnh if-then: Nhanh (nhanh hơn 3% so với giải pháp 'A')

Nhưng giải pháp của tôi nhanh hơn 4% so với giải pháp 'B':

N *= Math.Sign(1 + Math.Sign(N));
0 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ c# inline-if , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading