Làm thế nào để tối ưu hóa chức năng khoảng cách?


23

Trong khi phát triển một trò chơi tương tự RTS đơn giản, tôi nhận thấy các tính toán khoảng cách của tôi đang gây ra ảnh hưởng đến hiệu suất.

Tại mọi thời điểm, có các kiểm tra khoảng cách để biết liệu một đơn vị có nằm trong phạm vi của mục tiêu hay không, nếu đạn đã đạt được mục tiêu, nếu người chơi đã chạy qua một chiếc bán tải, va chạm chung, v.v. khoảng cách giữa hai điểm được sử dụng rất nhiều.

Câu hỏi của tôi là chính xác về điều đó. Tôi muốn biết các nhà phát triển trò chơi thay thế có gì để kiểm tra khoảng cách, ngoài cách tiếp cận sqrt (x * x + y * y) thông thường, khá tốn thời gian nếu chúng tôi thực hiện hàng nghìn lần trên mỗi khung hình.

Tôi muốn chỉ ra rằng tôi nhận thức được khoảng cách manhattan và so sánh khoảng cách bình phương (bằng cách bỏ qua nút cổ chai sqrt). Còn gì nữa không?



Ví dụ: nếu bạn có các đối tượng mà bạn không muốn di chuyển, như các tòa nhà, thì có thể đáng để lấy một chuỗi 2D taylor của hàm khoảng cách, cắt ngắn nó theo thuật ngữ vuông, sau đó lưu trữ hàm kết quả dưới dạng chức năng khoảng cách từ tòa nhà cụ thể đó. Điều đó sẽ chuyển một số công việc nặng nề sang khởi tạo và có thể tăng tốc mọi thứ lên một chút.
Alexander Gruber

Câu trả lời:


26

TL; DR; Vấn đề của bạn không phải là với việc thực hiện chức năng khoảng cách. Vấn đề của bạn là thực hiện chức năng khoảng cách rất nhiều lần. Nói cách khác, bạn cần tối ưu hóa thuật toán hơn là toán học.

[EDIT] Tôi đang xóa phần đầu tiên trong câu trả lời của mình, vì mọi người ghét nó. Tiêu đề câu hỏi đã yêu cầu các chức năng khoảng cách thay thế trước khi chỉnh sửa.

Bạn đang sử dụng hàm khoảng cách nơi bạn đang tính căn bậc hai mọi lúc. Tuy nhiên, bạn chỉ có thể thay thế mà không cần sử dụng căn bậc hai và tính khoảng cách bình phương thay thế. Điều này sẽ giúp bạn tiết kiệm rất nhiều chu kỳ quý giá.

Khoảng cách ^ 2 = x * x + y * y;

Đây thực sự là một mẹo phổ biến. Nhưng bạn cần điều chỉnh tính toán của mình cho phù hợp. Nó cũng có thể được sử dụng như kiểm tra ban đầu trước khi tính khoảng cách thực tế. Vì vậy, ví dụ thay vì tính khoảng cách thực tế giữa hai điểm / hình cầu cho một bài kiểm tra giao nhau, chúng ta có thể tính Khoảng cách bình phương thay thế và so sánh với bán kính bình phương thay vì bán kính.

Chỉnh sửa, ngay sau khi @ Byte56 chỉ ra rằng tôi đã không đọc câu hỏi và bạn đã biết về tối ưu hóa khoảng cách bình phương.

Thật không may, trong trường hợp của bạn, thật không may, chúng ta đang ở trong đồ họa máy tính gần như chỉ giao dịch với Không gian Euclide và khoảng cách được xác định chính xác như Sqrt of Vector dot itselftrong không gian euclide.

Bình phương khoảng cách là xấp xỉ tốt nhất bạn sẽ nhận được (về hiệu suất), tôi không thể thấy bất cứ điều gì đánh bại 2 phép nhân, một phép cộng và phép gán.

Vì vậy, bạn nói rằng tôi không thể tối ưu hóa chức năng khoảng cách, tôi nên làm gì?

Vấn đề của bạn không phải là với việc thực hiện chức năng khoảng cách. Vấn đề của bạn là thực hiện chức năng khoảng cách rất nhiều lần. Nói cách khác, bạn cần tối ưu hóa thuật toán hơn là toán học.

Vấn đề là, thay vì kiểm tra giao điểm của người chơi với từng đối tượng trong cảnh, từng khung. Bạn có thể dễ dàng sử dụng sự kết hợp không gian với lợi thế của mình và chỉ kiểm tra các đối tượng ở gần người chơi (có khả năng cao nhất là đánh / giao nhau.

Điều này có thể dễ dàng thực hiện bằng cách thực sự lưu trữ những thông tin không gian đó trong cấu trúc dữ liệu phân vùng không gian . Đối với một trò chơi đơn giản, tôi sẽ đề xuất một Lưới vì về cơ bản nó dễ thực hiện và phù hợp với cảnh động độc đáo.

Mỗi ô / hộp chứa một danh sách các đối tượng mà hộp giới hạn của lưới kèm theo. Và thật dễ dàng để theo dõi vị trí người chơi trong các ô đó. Và để tính toán khoảng cách, bạn chỉ kiểm tra khoảng cách người chơi với các đối tượng bên trong cùng một ô hoặc hàng xóm thay vì mọi thứ trong cảnh.

Một cách tiếp cận phức tạp hơn là sử dụng BSP hoặc Octrees.


2
Tôi tin rằng câu cuối cùng của câu hỏi nói rằng OP đang tìm kiếm các lựa chọn thay thế khác (họ biết về việc sử dụng khoảng cách bình phương).
MichaelHouse

@ Byte56 có bạn đúng, tôi đã không đọc nó.
Concept3d

Dù sao cũng cảm ơn bạn đã trả lời. Bạn có thêm một câu xác nhận rằng mặc dù phương pháp đó không cung cấp cho chúng ta khoảng cách euclide, nhưng nó rất chính xác khi so sánh? Tôi nghĩ rằng sẽ thêm một cái gì đó cho ai đó đến đây từ một công cụ tìm kiếm.
Grimshaw

@Grimshaw Tôi đã chỉnh sửa câu trả lời để giải quyết vấn đề ban đầu.
Concept3d

@ Byte56 cảm ơn đã chỉ ra. Tôi chỉnh sửa câu trả lời.
Concept3d

29

Nếu bạn cần một cái gì đó duy trì tuyến tính trên bất kỳ khoảng cách nào (không giống nhau distance^2) và vẫn xuất hiện một cách mơ hồ (không giống như khoảng cách Manhattan vuông vức và kim cương), bạn có thể lấy trung bình hai kỹ thuật sau để có được xấp xỉ khoảng cách hình bát giác:

dx = abs(x1 - x0)
dy = abs(y1 - y0)

dist = 0.5 * (dx + dy + max(dx, dy))

Đây là một hình ảnh (biểu đồ đường viền) của hàm, nhờ Wolfram Alpha :

Đường viền lô

Và đây là một biểu đồ của hàm lỗi của nó khi so sánh với khoảng cách euclide (chỉ radian, góc phần tư thứ nhất):

Lô lỗi

Như bạn có thể thấy, sai số dao động từ 0% trên các trục đến khoảng + 12% ở các thùy. Bằng cách sửa đổi các hệ số một chút, chúng tôi có thể giảm xuống còn +/- 4%:

dist = 0.4 * (dx + dy) + 0.56 * max(dx, dy)

nhập mô tả hình ảnh ở đây

Cập nhật

Sử dụng các hệ số trên, sai số tối đa sẽ nằm trong khoảng +/- 4%, nhưng sai số trung bình vẫn sẽ là + 1,3%. Tối ưu hóa cho lỗi trung bình bằng không, bạn có thể sử dụng:

dist = 0.394 * (dx + dy) + 0.554 * max(dx, dy)

trong đó có lỗi từ -5% đến + 3% và sai số trung bình là + 0,043%


Trong khi tìm kiếm trên web tên của thuật toán này, tôi đã tìm thấy xấp xỉ hình bát giác tương tự này :

dist = 1007/1024 * max(dx, dy) + 441/1024 * min(dx, dy)

Lưu ý rằng điều này về cơ bản là tương đương (mặc dù số mũ là khác nhau - những số mũ này có sai số -1,5% đến 7,5%, nhưng nó có thể được mát xa đến +/- 4%) bởi vì max(dx, dy) + min(dx, dy) == dx + dy. Sử dụng mẫu này, các cuộc gọi minmaxcó thể được thực hiện theo hướng có lợi cho:

if (dy > dx)
    swap(dx, dy)

dist = 1007/1024 * dx + 441/1024 * dy

Đây có phải là nhanh hơn phiên bản của tôi? Ai biết được ... phụ thuộc vào trình biên dịch và cách nó tối ưu hóa từng cái cho nền tảng đích. Tôi đoán là sẽ rất khó để thấy bất kỳ sự khác biệt.


3
Thật thú vị, chưa từng thấy điều này trước đây! Liệu nó có một cái tên, hay chỉ là "trung bình của Ch Quashev và Manhattan"?
congusbongus

@congusbongus Nó có thể có một cái tên, nhưng tôi không biết nó là gì. Nếu không, có lẽ một ngày nào đó nó sẽ được gọi là Khoảng cách Crist (hah ... có lẽ là không)
bcrist

1
Lưu ý rằng phép nhân dấu phẩy động không hiệu quả lắm. Đó là lý do tại sao phép tính gần đúng khác sử dụng 1007/1024 (sẽ được thực hiện dưới dạng phép nhân số nguyên theo sau là dịch chuyển bit).
MSalters

@MSalters Có, các phép toán dấu phẩy động thường chậm hơn các phép toán số nguyên, nhưng điều đó không liên quan - 0,4 và 0,56 có thể dễ dàng được chuyển đổi để sử dụng các phép toán số nguyên. Hơn nữa, trên phần cứng x86 hiện đại, hầu hết các hoạt động điểm nổi (trừ FDIV, FSQRTvà các chức năng siêu việt khác) chi phí về cơ bản giống như các phiên bản nguyên của họ: 1 hoặc 2 chu kỳ theo hướng dẫn.
bcrist

1
Điều này trông rất giống với Alpha max + Beta Min: en.wikipedia.org/wiki/Alpha_max_plus_beta_min_alacticm
Drainke7707

21

Đôi khi câu hỏi này có thể phát sinh không phải vì chi phí thực hiện tính toán khoảng cách, mà vì số lần tính toán được thực hiện.

Trong một lớn trò chơi thế giới với nhiều diễn viên, nó là không thể leo để liên tục kiểm tra khoảng cách giữa một diễn viên và tất cả những người khác. Khi ngày càng nhiều người chơi, NPC và projectiles nhập thế giới, số lượng so sánh mà cần phải được thực hiện sẽ phát triển bậc hai với O(N^2).

Một cách để giảm sự tăng trưởng đó là sử dụng cấu trúc dữ liệu tốt để nhanh chóng loại bỏ các tác nhân không mong muốn khỏi các tính toán.

Chúng tôi đang tìm cách lặp lại hiệu quả tất cả các diễn viên thể trong phạm vi, trong khi loại trừ phần lớn các diễn viên chắc chắn nằm ngoài phạm vi .

Nếu các diễn viên của bạn trải đều trên không gian thế giới, thì một lưới các thùng phải là một cấu trúc phù hợp (như câu trả lời được chấp nhận cho thấy). Bằng cách giữ các tham chiếu đến các diễn viên trong một lưới thô, bạn chỉ cần kiểm tra một vài trong số các thùng gần đó để bao gồm tất cả các diễn viên có thể nằm trong phạm vi, bỏ qua phần còn lại. Khi một diễn viên di chuyển, bạn có thể cần phải chuyển anh ta từ thùng cũ của mình sang một cái mới.

Đối với các diễn viên ít trải đều hơn một phần có thể làm tốt hơn cho thế giới hai chiều, hoặc một phần tám sẽ phù hợp với thế giới ba chiều. Đây là các cấu trúc mục đích chung hơn có thể phân vùng hiệu quả các khu vực lớn của không gian trống và các khu vực nhỏ chứa nhiều tác nhân. Đối với các tác nhân tĩnh,phân vùng không gian nhị phân (BSP), rất nhanh để tìm kiếm nhưng quá tốn kém để cập nhật trong thời gian thực. Các BSP tách không gian bằng cách sử dụng các mặt phẳng để liên tục cắt nó một nửa và có thể được áp dụng cho bất kỳ số lượng kích thước nào.

Tất nhiên, có những chi phí lớn để giữ cho các diễn viên của bạn có cấu trúc như vậy, đặc biệt là khi họ đang di chuyển giữa các phân vùng. Nhưng trong một thế giới rộng lớn với nhiều diễn viên nhưng phạm vi quan tâm nhỏ, chi phí sẽ thấp hơn nhiều so với những gì phải gánh chịu khi so sánh ngây thơ với mọi đối tượng.

Việc xem xét làm thế nào chi phí của một thuật toán tăng lên khi nó nhận được nhiều dữ liệu hơn là rất quan trọng đối với thiết kế phần mềm có thể mở rộng. Đôi khi chỉ cần chọn cấu trúc dữ liệu chính xác là đủ. Chi phí này thường được mô tả bằng Big O ký hiệu .

(Tôi nhận ra đây không phải là câu trả lời trực tiếp cho câu hỏi, nhưng nó có thể hữu ích cho một số độc giả. Tôi xin lỗi nếu tôi đã lãng phí thời gian của bạn!)


7
Đây là câu trả lời tốt nhất. Không có gì để tối ưu hóa trong chức năng khoảng cách; người ta chỉ cần sử dụng nó ít thường xuyên hơn.
sam hocevar

3
Câu trả lời được chấp nhận cũng bao gồm phân vùng không gian, nếu không câu trả lời của bạn thực sự tối ưu. Cảm ơn bạn.
Grimshaw

Thời gian của tôi đã dành rất tốt để đọc câu trả lời của bạn. Cảm ơn bạn, Joey.
Patrick M

1
Đây là câu trả lời tốt nhất và là câu trả lời duy nhất tập trung vào vấn đề thực sự thay vì cá trích đỏ về hiệu suất chức năng khoảng cách. Câu trả lời được chấp nhận cũng có thể bao gồm phân vùng không gian, nhưng nó là một bên; nó tập trung vào tính toán khoảng cách. Tính toán khoảng cách không phải là vấn đề chính ở đây; tối ưu hóa tính toán khoảng cách là một giải pháp phi vũ lực không có tỷ lệ.
Maximus Minimus

Bạn có thể giải thích tại sao số lượng so sánh sẽ là số mũ? Tôi mặc dù nó sẽ là bậc hai, so sánh từng diễn viên với nhau trong mỗi khung thời gian.
Petr Pudlák

4

Làm thế nào về khoảng cách Ch Quashev? Đối với các điểm p, q nó được định nghĩa như sau:

khoảng cách

Vì vậy, đối với các điểm (2, 4) và (8, 5), khoảng cách Ch Quashev là 6, như | 2-8 | > | 4-5 |.

Hơn nữa, gọi E là khoảng cách Euclide và C là khoảng cách Ch Quashev. Sau đó:

khoảng cách2

Giới hạn trên có thể không được sử dụng nhiều vì bạn phải tính căn bậc hai, nhưng giới hạn dưới có thể hữu ích - bất cứ khi nào khoảng cách Ch Quashev đủ lớn để nằm ngoài phạm vi, khoảng cách Euclide cũng phải, giúp bạn tiết kiệm từ việc phải tính toán nó

Dĩ nhiên, sự đánh đổi là nếu khoảng cách Ch Quashev nằm trong phạm vi, bạn sẽ phải tính toán khoảng cách Euclide dù sao, sẽ lãng phí thời gian. Chỉ có một cách để tìm hiểu xem nó sẽ là một chiến thắng ròng!


1
Bạn cũng có thể sử dụng khoảng cách Manhattan như một hướng trên.
congusbongus

1
Đúng đủ rồi. Tôi cho rằng từ đó chỉ là một bước nhảy, bỏ qua và nhảy đến "mức trung bình của Ch Quashev và Manhattan" theo đề xuất của bcrist.
Tết

2

Tối ưu hóa cục bộ rất đơn giản là chỉ cần kiểm tra một chiều duy nhất trước tiên.

Đó là :

distance ( x1, y1 , x1, y2) > fabs (x2 - x1)

Vì vậy, chỉ cần kiểm tra fabs (x2 - x1)như một bộ lọc đầu tiên có thể mang lại lợi ích đáng kể. Bao nhiêu sẽ phụ thuộc vào kích thước của thế giới so với các phạm vi có liên quan.

Hơn nữa, bạn có thể sử dụng điều này như là một thay thế cho cấu trúc dữ liệu phân vùng không gian.

Nếu tất cả các đối tượng có liên quan được sắp xếp trong một danh sách theo thứ tự tọa độ x, thì các đối tượng gần đó phải ở gần trong danh sách. Ngay cả khi danh sách không còn hoạt động do không được duy trì hoàn toàn khi các vật thể di chuyển, thì với các giới hạn tốc độ đã biết, bạn vẫn có thể giảm phần danh sách cần tìm kiếm các vật thể gần đó.


2

Những nỗ lực đã được thực hiện trong quá khứ để tối ưu hóa sqrt. Mặc dù nó không còn áp dụng cho các máy hiện nay, nhưng đây là một ví dụ từ mã nguồn Quake, sử dụng số ma thuật 0x5f3759df :

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;  // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 ); // what the hell?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration (optional)
  // ...
  return y;
}

Một lời giải thích chi tiết về những gì đang diễn ra ở đây có thể được tìm thấy trên Wikipedia.

Nói tóm lại, đó là một vài lần lặp lại của phương pháp Newton (một thuật toán số giúp lặp lại ước tính), với số ma thuật được sử dụng để đưa ra ước tính ban đầu hợp lý.

Như Travis chỉ ra, loại tối ưu hóa này không còn hữu ích trên các kiến ​​trúc hiện đại. Và ngay cả khi nó là, nó chỉ có thể cung cấp một tốc độ tăng tốc không đổi cho nút cổ chai của bạn, trong khi thiết kế lại thuật toán có thể đạt được kết quả tốt hơn.


2
Đây không phải là một tối ưu hóa đáng giá nữa. Hầu hết tất cả các kiến ​​trúc PC cấp tiêu dùng bạn có thể mua hiện nay đều có các hướng dẫn sqrt được tối ưu hóa phần cứng, thực hiện căn bậc hai trong một chu kỳ đồng hồ hoặc ít hơn. Nếu bạn thực sự cần sqrt nhanh nhất có thể, bạn sử dụng hướng dẫn sqrt dấu phẩy động x86 simd: en.wikipedia.org/wiki/iêu Đối với những thứ như shader trên GPU, gọi sqrt sẽ tự động dẫn đến một hướng dẫn như vậy. Trên CPU, tôi giả sử nhiều trình biên dịch triển khai sqrt qua SIMD sqrt nếu có.
TravisG

@TravisG Vâng, đó là điều đáng nói, vì vậy tôi đã cập nhật câu trả lời. Câu trả lời này chỉ được cung cấp cho niềm vui và lợi ích lịch sử!
joeytwiddle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.