2009-02-13 30 views
8

Tôi không thực sự biết nhiều về nội bộ của trình biên dịch và tối ưu hóa JIT, nhưng tôi thường cố gắng sử dụng "ý thức chung" để đoán những gì có thể được tối ưu hóa và những gì không thể. Vì vậy, hôm nay tôi đã viết một phương pháp thử đơn vị đơn giản:Làm cách nào để viết mã (kiểm tra) sẽ không được tối ưu hóa bởi trình biên dịch/JIT?

@Test // [Test] in C# 
public void testDefaultConstructor() { 
    new MyObject(); 
} 

Phương pháp này thực sự là tất cả những gì tôi cần. Nó kiểm tra rằng constructor mặc định tồn tại và chạy mà không có ngoại lệ.

Nhưng sau đó tôi bắt đầu nghĩ về hiệu ứng của tối ưu hóa trình biên dịch/JIT. Trình biên dịch/JIT có thể tối ưu hóa phương pháp này bằng cách loại bỏ hoàn toàn câu lệnh new MyObject(); không? Tất nhiên, nó sẽ cần phải xác định rằng đồ thị cuộc gọi không có tác dụng phụ cho các đối tượng khác, đó là trường hợp điển hình cho một nhà xây dựng bình thường mà chỉ đơn giản là khởi tạo trạng thái nội bộ của đối tượng.

Tôi cho rằng chỉ JIT mới được phép thực hiện tối ưu hóa như vậy. Điều này có thể có nghĩa rằng nó không phải là một cái gì đó tôi nên lo lắng về, bởi vì phương pháp thử nghiệm đang được thực hiện chỉ một lần. Giả định của tôi có đúng không?

Tuy nhiên, tôi đang cố gắng nghĩ về chủ đề chung. Khi tôi nghĩ về cách ngăn chặn phương pháp này được tối ưu hóa, tôi nghĩ tôi có thể assertTrue(new MyObject().toString() != null), nhưng điều này phụ thuộc rất nhiều vào việc thực hiện phương thức toString() và thậm chí sau đó, JIT có thể xác định phương thức toString() luôn trả về một giá trị không chuỗi (ví dụ: nếu thực tế là Object.toString() đang được gọi), và do đó tối ưu hóa toàn bộ nhánh. Vì vậy, cách này sẽ không hoạt động.

Tôi biết rằng trong C# tôi có thể sử dụng [MethodImpl(MethodImplOptions.NoOptimization)], nhưng đây không phải là những gì tôi thực sự đang tìm kiếm. Tôi hy vọng sẽ tìm được cách (không độc lập) để đảm bảo rằng một số phần cụ thể của mã của tôi sẽ thực sự chạy như tôi mong đợi, mà không có JIT can thiệp vào quá trình này.

Ngoài ra, có bất kỳ trường hợp tối ưu hóa điển hình nào tôi nên biết khi tạo các bài kiểm tra đơn vị của mình không?

Cảm ơn rất nhiều!

Trả lời

4

Đừng lo lắng về điều đó. Nó không được phép tối ưu hóa bất cứ thứ gì có thể tạo sự khác biệt cho hệ thống của bạn (ngoại trừ tốc độ). Nếu bạn mới một đối tượng, mã được gọi là, bộ nhớ được phân bổ, nó đã làm việc.

Nếu bạn có nó được bảo vệ bởi if (false), nếu sai là kết thúc, nó có thể được tối ưu hóa hoàn toàn khỏi hệ thống, sau đó nó có thể phát hiện ra rằng phương pháp không làm bất cứ điều gì và tối ưu hóa CNTT (trong học thuyết).

Edit: bằng cách này, nó có thể cũng đủ thông minh để xác định rằng phương pháp này:

newIfTrue(boolean b) { 
    if(b) 
     new ThisClass(); 
} 

sẽ luôn luôn không phải làm gì nếu b là sai, và cuối cùng tìm ra rằng tại một thời điểm trong mã B của bạn luôn luôn là sai và biên dịch thói quen này ra khỏi mã đó hoàn toàn.

Đây là nơi mà JIT có thể thực hiện những thứ hầu như không thể thực hiện được trong bất kỳ ngôn ngữ không được quản lý nào.

+0

Cảm ơn bạn. Tôi tự hỏi tại sao JIT cư xử theo cách này? Nếu phân bổ đối tượng là vô ích (như thực sự có thể được xác định bằng phân tích tĩnh trong một số trường hợp), tại sao JIT sẽ không tối ưu hóa nó? –

+0

Bây giờ tôi có thể nghĩ về một trường hợp góc, nhưng tôi nghĩ nó đủ hiếm.Nếu phân bổ đối tượng đã được thực hiện ví dụ để đảm bảo rằng đủ bộ nhớ có sẵn cho một số đối tượng khác (và thậm chí đảm bảo không có phân trang sẽ xảy ra), tối ưu hóa sẽ làm mất hiệu lực giả định. –

+0

Cần phải rời khỏi Máy ảo Java (JVM) trong trạng thái phù hợp với việc có mã chương trình thực thi trong JVM theo Mô hình bộ nhớ Java. Không bắt buộc phải thực thi bất kỳ mã cụ thể nào hoặc cấp phát bộ nhớ trong trường hợp JIT có thể chứng minh rằng mã không có hiệu lực trên trạng thái chương trình quan sát được. –

5

Tôi nghĩ nếu bạn lo lắng về việc nó được tối ưu hóa, bạn có thể đang làm một chút thử nghiệm quá mức cần thiết.

Trong ngôn ngữ tĩnh, tôi có xu hướng nghĩ về trình biên dịch dưới dạng thử nghiệm. Nếu nó vượt qua biên dịch, điều đó có nghĩa là có một số thứ nhất định (như phương pháp). Nếu bạn không có một bài kiểm tra khác mà thực thi hàm tạo mặc định của bạn (điều này sẽ chứng minh nó sẽ không ném ngoại lệ), bạn có thể nghĩ về lý do tại sao bạn đang viết hàm tạo mặc định đó ngay từ đầu (YAGNI và tất cả).

Tôi biết có những người không đồng ý với tôi, nhưng tôi cảm thấy những thứ như thế này chỉ là một thứ sẽ làm tăng số lượng bài kiểm tra của bạn mà không có lý do hữu ích nào, thậm chí nhìn qua kính TDD.

+0

Có, kiểm tra quá mức ftl. – MichaelGG

+0

Tôi đồng ý với bạn. Khi tôi nghĩ về nó (sau khi đọc câu trả lời của bạn), phải có các bài kiểm tra khác sử dụng hàm tạo mặc định nếu thử nghiệm này quan trọng hơn là chỉ là một bài kiểm tra "được biên dịch". Cảm ơn! –

+0

Trong một ngôn ngữ như Java, môi trường biên dịch và thực thi có thể khác nhau rất nhiều. Một thử nghiệm như thế này có thể có ý nghĩa nếu lớp được xây dựng nằm riêng biệt với mã kiểm tra. Đặc biệt thú vị sẽ có một trình nạp lớp tùy chỉnh tải lớp học trực tiếp từ một số hình thức lưu trữ động - mã này sẽ đảm bảo rằng tra cứu thực sự hoạt động. –

0

Tại sao nó lại quan trọng? Nếu trình biên dịch/JIT có thể xác định tĩnh không có xác nhận nào sẽ bị trúng (có thể gây ra các tác dụng phụ), thì bạn vẫn ổn.

+0

Nó cũng cần xác minh rằng hàm tạo tồn tại, không ảnh hưởng đến trạng thái chương trình tĩnh, và không thể ném một ngoại lệ được yêu cầu ngầm bởi JVM như 'NullPointerException'. –

2

Hãy suy nghĩ về nó theo cách này:

Cho phép giả định rằng trình biên dịch có thể xác định rằng đồ thị cuộc gọi không có bất kỳ tác dụng phụ (tôi không nghĩ rằng đó là có thể, tôi mơ hồ nhớ một cái gì đó về P = NP từ các khóa học CS của tôi). Nó sẽ tối ưu hóa bất kỳ phương pháp nào không có tác dụng phụ. Vì hầu hết các thử nghiệm không có và không nên có bất kỳ tác dụng phụ nào sau đó trình biên dịch có thể tối ưu hóa tất cả chúng.

+0

Ý tưởng tuyệt vời! Tôi không nghĩ về điều đó. :) –

1

Dường như trong C# tôi có thể làm điều này:

[Test] 
public void testDefaultConstructor() { 
    GC.KeepAlive(new MyObject()); 
} 

AFAIU, phương pháp GC.KeepAlive sẽ không được inlined bởi JIT, vì vậy mã sẽ được đảm bảo để làm việc như mong đợi. Tuy nhiên, tôi không biết một cấu trúc tương tự trong Java.

+1

-1: gây hiểu nhầm. Đây không phải là trường hợp mà 'GC.KeepAlive' là bắt buộc (hoặc thậm chí cung cấp bất kỳ lợi ích nào cả). –

0

Mỗi I/O là một tác dụng phụ, vì vậy bạn chỉ có thể đặt

Object obj = new MyObject(); 
System.out.println(obj.toString()); 

và bạn đang sử dụng tốt.

+0

Vâng, đây chắc chắn là một cách để làm điều đó. Nhưng kiểm tra đơn vị thường không nên có báo cáo đầu ra. –

+0

Tôi nghĩ rằng miễn là bạn không dựa vào I/O để xác định xem thử nghiệm đã trôi qua hay thất bại, bạn vẫn ổn. –

+1

-1: gây hiểu nhầm. Anh ấy ổn mà không có I/O. Câu trả lời này làm cho nó xuất hiện như xét nghiệm đơn vị của mẫu đơn này cần I/O để đảm bảo tính chính xác. –

2

JIT chỉ được phép thực hiện các thao tác không ảnh hưởng đến ngữ nghĩa được bảo đảm của ngôn ngữ. Về mặt lý thuyết, nó có thể loại bỏ phân bổ và gọi đến nhà xây dựng MyObjectnếu nó có thể đảm bảo rằng cuộc gọi không có tác dụng phụ và không bao giờ có thể ném ngoại lệ (không tính OutOfMemoryError).

Nói cách khác, nếu JIT tối ưu hóa cuộc gọi ra khỏi thử nghiệm của bạn, khi đó kiểm tra của bạn sẽ vượt qua được bất kỳ số.

PS: Lưu ý rằng điều này áp dụng bởi vì bạn đang thực hiện chức năng thử nghiệm thay vì hiệu suất thử nghiệm. Trong thử nghiệm hiệu suất, điều quan trọng là phải đảm bảo JIT không tối ưu hóa hoạt động bạn đang đo lường, kết quả khác của bạn trở nên vô dụng.

+0

Chế độ xem tuyệt vời về chủ đề. Một bổ sung: Khi sử dụng kiểm tra hiệu suất cho chức năng (ví dụ như cố gắng tìm ra kích thước bộ nhớ cache của CPU), chúng ta nên thêm '[MethodImpl (MethodImplOptions.NoOptimization)]' để tránh JIT thực hiện các thủ thuật. –

Các vấn đề liên quan