实现的JNI接口,同一套代码,为什么在windows下性能比JAVA实现要好很多。但在linux下实现性能却比JAVA实现差很多。以下是具体数据:
在Windows 11 下(4核8线程 Intel(R) Core(TM)i7-10510U CPU @ 1.80GHz)
项目是navmesh 3D寻路,
📄 性能测试结果如下:
在Windows 11 下(64位,4核8线程 Intel(R) Core(TM)i7-10510U CPU @ 1.80GHz)
Benchmark Mode Cnt Score Error Units
NavEngineBenchMark.javaFind thrpt 10 28605.626 ± 1331.775 ops/s
NavEngineBenchMark.javaFindNearest thrpt 10 497177.122 ± 12048.418 ops/s
NavEngineBenchMark.javaRaycast thrpt 10 401200.127 ± 7926.908 ops/s
NavEngineBenchMark.nativeFind thrpt 10 82924.746 ± 1942.112 ops/s
NavEngineBenchMark.nativeFindNearest thrpt 10 1438972.883 ± 28769.610 ops/s
NavEngineBenchMark.nativeRaycast thrpt 10 1096445.690 ± 29597.755 ops/s
- 寻路API,性能达到了原先的289.89%;
- 寻找最近可通点API,性能达到了原先的289.43%;
- 光线照反射API,性能达到了原先的273.29%;
在CentOS Linux 7 (64位,8核16线程 Intel(R) Xeon(R) Platinum 8372C CPU model 106 @ 3.20GHz)
Benchmark Mode Cnt Score Error Units
NavEngineBenchMark.javaFind thrpt 10 38372.243 ± 80.293 ops/s
NavEngineBenchMark.javaFindNearest thrpt 10 518859.216 ± 767.435 ops/s
NavEngineBenchMark.javaRaycast thrpt 10 442360.257 ± 287.334 ops/s
NavEngineBenchMark.nativeFind thrpt 10 26796.756 ± 49.669 ops/s
NavEngineBenchMark.nativeFindNearest thrpt 10 393484.307 ± 844.553 ops/s
NavEngineBenchMark.nativeRaycast thrpt 10 305434.422 ± 851.248 ops/s
- 寻路API,性能降到了原先的69.83%;
- 寻找最近可通点API,性能降到了原先的 75.84%;
- 光线照反射API,性能降到了原先的69.05%;
jmh 性能对比项目地址为:https://github.com/jiangguilong2000/gamioo/
jni 项目为: 为https://github.com/jiangguilong2000/recastnavigation
主要代码维护在RecastDll里的NavEngine.cpp,对应到JAVA的是io.gamioo.nav.NavEngine.
项目是navmesh 3D寻路,在IDEA下,运行NavEngineBenchMark直接跑起来,或者在windows下和linux下到gamioo目录,执行gradle gamioo-navigation:jmh,能直接跑出如上结果
由于有部分小伙伴提到需要给出源码,才能给出建议,现给出如下:
主要5个文件,
io_gamioo_nav_NavEngine.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class io_gamioo_nav_NavEngine */
#ifndef _Included_io_gamioo_nav_NavEngine
#define _Included_io_gamioo_nav_NavEngine
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: io_gamioo_nav_NavEngine
* Method: load
* Signature: (I[BI)I
*/
JNIEXPORT jint JNICALL Java_io_gamioo_nav_NavEngine_load
(JNIEnv *, jobject, jint, jbyteArray, jint);
/*
* Class: io_gamioo_nav_NavEngine
* Method: find
* Signature: (IFFFFFF)[F
*/
JNIEXPORT jfloatArray JNICALL Java_io_gamioo_nav_NavEngine_find
(JNIEnv *, jobject, jint, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat);
/*
* Class: io_gamioo_nav_NavEngine
* Method: findNearest
* Signature: (IFFF)[F
*/
JNIEXPORT jfloatArray JNICALL Java_io_gamioo_nav_NavEngine_findNearest
(JNIEnv *, jobject, jint, jfloat, jfloat, jfloat);
/*
* Class: io_gamioo_nav_NavEngine
* Method: raycast
* Signature: (IFFFFFF)[F
*/
JNIEXPORT jfloatArray JNICALL Java_io_gamioo_nav_NavEngine_raycast
(JNIEnv *, jobject, jint, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat);
/*
* Class: io_gamioo_nav_NavEngine
* Method: release
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_io_gamioo_nav_NavEngine_release
(JNIEnv *, jobject, jint);
/*
* Class: io_gamioo_nav_NavEngine
* Method: releaseAll
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_io_gamioo_nav_NavEngine_releaseAll
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
NavEngine.h
#include <cstring>
#include <unordered_map>
#include <iostream>
#include <exception>
#include <string>
#include <cstdint>
#include <map>
#include <sstream>
class NavMeshContex;
class NavMesh
{
public:
static NavMesh* instance;
static NavMesh* GetInstace();
std::map<int32_t, NavMeshContex*> navMeshContexs;
NavMeshContex* New(int32_t id, const char* buffer, int32_t n);
NavMeshContex* Get(int32_t id);
void Clear();
const char* Version();
void Remove(int32_t id);
private:
NavMesh();
};
struct NavMeshSetHeader
{
int magic;
int version;
int numTiles;
dtNavMeshParams params;
};
struct NavMeshTileHeader
{
dtTileRef tileRef;
int dataSize;
};
Util.h
#pragma once
namespace Util
{
/**JByteaArray -> char* */
static char* ConvertJByteaArrayToChars(JNIEnv* env, jbyteArray bytearray){
char* chars = NULL;
jbyte* bytes;
bytes = env->GetByteArrayElements(bytearray, 0);
size_t chars_len = env->GetArrayLength(bytearray);
chars = new char[chars_len + 1];
memset(chars, 0, chars_len + 1);
memcpy(chars, bytes, chars_len);
chars[chars_len] = 0;
env->ReleaseByteArrayElements(bytearray, bytes, 0);
return chars;
}
/** float* -> jfloatArray */
static jfloatArray ConvertFloatStarToJfloatArray(JNIEnv* env, float* array, int length) {
jfloatArray ret = env->NewFloatArray(length);
env->SetFloatArrayRegion(ret, 0, length, array);
return ret;
}
};
核心代码: NavEngine.cpp
#include "DetourNavMesh.h"
#include "jni.h"
#include "DetourNavMeshQuery.h"
#include <cstring>
#include <unordered_map>
#include "DetourCommon.h"
#include <iostream>
#include <exception>
#include <string>
#include <cstdint>
#include <map>
#include "io_gamioo_nav_NavEngine.h"
#include "Util.h"
#include "NavEngine.h"
#include <sstream>
using namespace std;
static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET';
static const int NAVMESHSET_VERSION = 1;
static const float extents[3] = {1.0f,1.0f,1.0f};
static const float raycastExtents[3] = { 0.0f,1.0f,0.0f };
int32_t InitNav(const char* buffer, int32_t n, dtNavMesh*& navMesh)
{
int index = 0;
// Read header.
NavMeshSetHeader header;
int count = sizeof(NavMeshSetHeader);
if (index + count > n)
{
return -1;
}
memcpy(&header, buffer + index, count);
index += count;
if (header.magic != NAVMESHSET_MAGIC)
{
return -2;
}
if (header.version != NAVMESHSET_VERSION)
{
return -3;
}
dtNavMesh* mesh = dtAllocNavMesh();
if (!mesh)
{
return -4;
}
dtStatus status = mesh->init(&header.params);
if (dtStatusFailed(status))
{
return -5;
}
// Read tiles.
for (int i = 0; i < header.numTiles; ++i)
{
NavMeshTileHeader tileHeader;
count = sizeof(NavMeshTileHeader);
if (index + count > n)
{
return -6;
}
memcpy(&tileHeader, buffer + index, count);
index += count;
if (!tileHeader.tileRef || !tileHeader.dataSize)
break;
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
if (!data) break;
memset(data, 0, tileHeader.dataSize);
count = tileHeader.dataSize;
if (index + count > n)
{
return -7;
}
memcpy(data, buffer + index, count);
index += count;
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
}
navMesh = mesh;
return 0;
}
static const int MAX_POLYS = 256;
static const int MAX_SMOOTH = 2048;
class NavMeshContex
{
public:
dtNavMesh* navMesh;
dtNavMeshQuery* navQuery;
int32_t Init(const char* buffer, int32_t n)
{
int32_t ret = InitNav(buffer, n, navMesh);
std::string s;
if (ret != 0)
{
return -1;
}
navQuery = new dtNavMeshQuery();
navQuery->init(navMesh, 2048);
return 0;
}
~NavMeshContex()
{
if (navQuery != nullptr)
{
dtFreeNavMeshQuery(navQuery);
}
if (navMesh != nullptr)
{
dtFreeNavMesh(navMesh);
}
}
std::string toString() {
std::stringstream ss;
ss << "NavMeshContex[" << static_cast<const void*>(this) << "]";
return ss.str();
}
};
NavMesh* NavMesh::instance = nullptr;
NavMesh::NavMesh()
{
}
NavMesh* NavMesh::GetInstace()
{
if (NavMesh::instance == nullptr)
{
NavMesh::instance = new NavMesh();
}
return NavMesh::instance;
}
NavMeshContex* NavMesh::New(int32_t id, const char* buffer, int32_t n)
{
NavMeshContex* navMeshContex = new NavMeshContex();
int32_t ret = navMeshContex->Init(buffer, n);
if (ret != 0)
{
delete navMeshContex;
return nullptr;
}
navMeshContexs[id] = navMeshContex;
return navMeshContex;
}
NavMeshContex* NavMesh::Get(int32_t id)
{
const auto it = navMeshContexs.find(id);
if (it != navMeshContexs.end())
{
return it->second;
}
return nullptr;
}
void NavMesh::Clear()
{
for (auto kv : navMeshContexs)
{
delete kv.second;
}
navMeshContexs.clear();
}
void NavMesh::Remove(int32_t id)
{
const auto it = navMeshContexs.find(id);
if (it != navMeshContexs.end())
{
NavMeshContex* navMesh = it->second;
navMeshContexs.erase(it);
delete navMesh;
}
}
const char* NavMesh::Version()
{
return ""+ NAVMESHSET_VERSION;
}
/**
* 加载地图
*
* @param id 寻路数据地图ID
* @param content 地图文件的路径例
* @param length 数据长度
* @return navmeshId, 为0或负数表示加载失败,为正数表示加载成功,后续寻路时传入此id为参数
*/
JNIEXPORT jint JNICALL Java_io_gamioo_nav_NavEngine_load(JNIEnv* env, jobject jobj, jint id, jbyteArray content, jint length)
{
char* buffer = Util::ConvertJByteaArrayToChars(env, content);
NavMeshContex* context=NavMesh::GetInstace()->New(id, buffer, length);
delete buffer;
if (context== nullptr) {
return -1;
}
else {
return id;
}
}
NavMeshContex* RecastGet(int32_t id)
{
return NavMesh::GetInstace()->Get(id);
}
/**
* 寻路
*
* @param navmeshId 寻路数据地图ID
* @param startX 起始点X
* @param startY 起始点Y
* @param endX 结束点X
* @param endY 结束点Y
* @return 返回路径点列表,注意,查找不到时,会返回空
*/
JNIEXPORT jfloatArray JNICALL Java_io_gamioo_nav_NavEngine_find(JNIEnv* env, jobject jobj, jint id, jfloat startX, jfloat startY, jfloat startZ, jfloat endX, jfloat endY, jfloat endZ)
{
jfloatArray ret= NULL;
float straightPath[MAX_POLYS * 3];
float startPos[3] = {startX ,startY,startZ };
float endPos[3] = {endX ,endY,endZ };
dtPolyRef startRef = 0;
dtPolyRef endRef = 0;
float startNearestPt[3];
float endNearestPt[3];
dtQueryFilter filter;
filter.setIncludeFlags(0xffff);
filter.setExcludeFlags(0);
NavMeshContex* navMeshContex =RecastGet(id);
if (navMeshContex==nullptr)
{
return ret;
}
//cout << navMeshContex->toString()<<endl;
int dtStatus =navMeshContex->navQuery->findNearestPoly(startPos, extents, &filter, &startRef, startNearestPt);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
dtStatus =navMeshContex->navQuery->findNearestPoly(endPos, extents, &filter, &endRef, endNearestPt);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
dtPolyRef polys[MAX_POLYS];
int npolys;
unsigned char straightPathFlags[MAX_POLYS];
dtPolyRef straightPathPolys[MAX_POLYS];
int nstraightPath = 0;
dtStatus=navMeshContex->navQuery->findPath(startRef, endRef, startNearestPt, endNearestPt, &filter, polys, &npolys, MAX_POLYS);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
//printf("npolys: %d\n", npolys);
if (npolys)
{
float epos1[3];
dtVcopy(epos1, endNearestPt);
if (polys[npolys - 1] != endRef)
{
dtStatus=navMeshContex->navQuery->closestPointOnPoly(polys[npolys - 1], endNearestPt, epos1, 0);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
}
dtStatus = navMeshContex->navQuery-> findStraightPath(startNearestPt, endNearestPt, polys, npolys, straightPath, straightPathFlags, straightPathPolys, &nstraightPath, MAX_POLYS, 0);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
ret= Util::ConvertFloatStarToJfloatArray(env, straightPath,nstraightPath * 3);
}
return ret;
}
/**
* 光线照射发,寻找可以支线通过的hit点,如果可通过则返回hit
*
* @param navmeshId 寻路数据地图ID
* @param startX 起始点X
* @param startY 起始点Y
* @param endX 结束点X
* @param endY 结束点Y
* @return 返回射线射过去遇到的第一个阻挡点,如果到终点都没有阻挡,返回终点
*/
JNIEXPORT jfloatArray JNICALL Java_io_gamioo_nav_NavEngine_raycast(JNIEnv* env, jobject jobj, jint id, jfloat startX, jfloat startY, jfloat startZ, jfloat endX, jfloat endY, jfloat endZ)
{
jfloatArray ret = NULL;
float hitPoint[3];
float startPos[3] = { startX ,startY,startZ };
float endPos[3] = { endX ,endY,endZ };
dtPolyRef startRef = 0;
dtPolyRef endRef = 0;
float startNearestPt[3];
//float endNearestPt[3];
dtQueryFilter filter;
filter.setIncludeFlags(0xffff);
filter.setExcludeFlags(0);
NavMeshContex* navMeshContex = RecastGet(id);
if (navMeshContex == nullptr)
{
return ret;
}
//cout << navMeshContex->toString() << endl;
int dtStatus = navMeshContex->navQuery->findNearestPoly(startPos, raycastExtents, &filter, &startRef, startNearestPt);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
// dtStatus = navMeshContex->navQuery->findNearestPoly(endPos, raycastExtents, &filter, &endRef, endNearestPt);
// if ((dtStatus & DT_SUCCESS) == 0)
// {
// return ret;
// }
dtPolyRef polys[MAX_POLYS];
int npolys;
int nstraightPath = 0;
float t = 0;
float hitNormal[3];
memset(hitNormal, 0, sizeof(hitNormal));
//printf("version: %d\n", 3);
dtStatus = navMeshContex->navQuery->raycast(startRef, startNearestPt, endPos, &filter, &t, hitNormal, polys, &npolys, MAX_POLYS);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
// printf("endPos: %f\n", t);
if (t > 1) {
// no hit;
ret = Util::ConvertFloatStarToJfloatArray(env, endPos, 3);
return ret;
}
else {
// hit
hitPoint[0] = startPos[0] + (endPos[0] - startPos[0]) * t;
hitPoint[1] = startPos[1] + (endPos[1] - startPos[1]) * t;
hitPoint[2] = startPos[2] + (endPos[2] - startPos[2]) * t;
// printf("hitPoint: %f,%f,%f\n", hitPoint[0], hitPoint[1], hitPoint[2]);
// printf("npolys: %d\n", npolys);
ret = Util::ConvertFloatStarToJfloatArray(env, hitPoint, 3);
//if (npolys) {
// float h = 0;
// dtStatus = navMeshContex->navQuery->getPolyHeight(polys[npolys - 1], hitPoint, &h);
// if ((dtStatus & DT_SUCCESS) == 0)
// {
// return ret;
// }
// hitPoint[1] = h;
// ret = Util::ConvertFloatStarToJfloatArray(env, hitPoint, 3);
//}
}
return ret;
}
/**
* 找到目标点最近的静态可达点
*
* @param navmeshId 寻路数据地图ID
* @param pointX 参考点X
* @param pointY 参考点Y
* @return 如果目标点可达,返回目标点即可, 如果搜索范围内没有,返回空
*/
JNIEXPORT jfloatArray JNICALL Java_io_gamioo_nav_NavEngine_findNearest(JNIEnv* env, jobject jobj, jint id, jfloat pointX, jfloat pointY,jfloat pointZ)
{
jfloatArray ret = NULL;
float nearestPos[3]={0.0f};
float startPos[3] = { pointX ,pointY,pointZ };
dtPolyRef startRef = 0;
dtQueryFilter filter;
filter.setIncludeFlags(0xffff);
filter.setExcludeFlags(0);
NavMeshContex* navMeshContex = RecastGet(id);
if (navMeshContex == nullptr)
{
return ret;
}
int dtStatus = navMeshContex->navQuery->findNearestPoly(startPos, extents, &filter, &startRef, nearestPos);
if ((dtStatus & DT_SUCCESS) == 0)
{
return ret;
}
if (nearestPos[0]*10000==0&& nearestPos[1] * 10000 == 0 && nearestPos[2] * 10000 == 0) {
// printf("nearestPos: %f %f %f\n", nearestPos[0], nearestPos[1], nearestPos[2]);
return ret;
}
ret = Util::ConvertFloatStarToJfloatArray(env, nearestPos, 3);
return ret;
}
/**
* 释放加载的地图数据
*
* @param navmeshId 寻路数据地图ID
*/
JNIEXPORT void JNICALL Java_io_gamioo_nav_NavEngine_release(JNIEnv* env, jobject jobj, jint id)
{
NavMesh::GetInstace()->Remove(id);
}
/**
* 释放加载的所有地图数据
*/
JNIEXPORT void JNICALL Java_io_gamioo_nav_NavEngine_releaseAll(JNIEnv* env, jobject jobj)
{
NavMesh::GetInstace()->Clear();
}
编译脚本 CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
find_package(JNI REQUIRED)
# Use C++11
set(CMAKE_CXX_STANDARD 11)
# Require (at least) it
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Don't use e.g. GNU extension (like -std=gnu++11) for portability
set(CMAKE_CXX_EXTENSIONS OFF)
include_directories(${JNI_INCLUDE_DIRS})
if ( WIN32 AND NOT CYGWIN AND NOT ( CMAKE_SYSTEM_NAME STREQUAL "WindowsStore" ) )
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT" CACHE STRING "")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd" CACHE STRING "")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT" CACHE STRING "")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd" CACHE STRING "")
endif ()
project(RecastDll)
find_path(RecastDll_PROJECT_DIR NAMES SConstruct
PATHS
${CMAKE_SOURCE_DIR}
NO_DEFAULT_PATH
)
MARK_AS_ADVANCED(RecastDll_PROJECT_DIR)
# 配置cpp文件
file(GLOB RECASTDLL_SOURCES
Source/*.cpp
../Detour/Source/*.cpp
../DetourCrowd/Source/*.cpp
../DetourTileCache/Source/*.cpp
../Recast/Source/*.cpp
)
# 配置头文件
include_directories(
Include
../DebugUtils/Include
../Detour/Include
../DetourCrowd/Include
../DetourTileCache/Include
../Recast/Include
)
macro(source_group_by_dir proj_dir source_files)
if(MSVC)
get_filename_component(sgbd_cur_dir ${proj_dir} ABSOLUTE)
foreach(sgbd_file ${${source_files}})
get_filename_component(sgbd_abs_file ${sgbd_file} ABSOLUTE)
file(RELATIVE_PATH sgbd_fpath ${sgbd_cur_dir} ${sgbd_abs_file})
string(REGEX REPLACE "\(.*\)/.*" \\1 sgbd_group_name ${sgbd_fpath})
string(COMPARE EQUAL ${sgbd_fpath} ${sgbd_group_name} sgbd_nogroup)
string(REPLACE "/" "\\" sgbd_group_name ${sgbd_group_name})
if(sgbd_nogroup)
set(sgbd_group_name "\\")
endif(sgbd_nogroup)
source_group(${sgbd_group_name} FILES ${sgbd_file})
endforeach(sgbd_file)
endif(MSVC)
endmacro(source_group_by_dir)
source_group_by_dir(${CMAKE_CURRENT_SOURCE_DIR} RECASTDLL_SOURCES)
add_library(RecastDll SHARED ${RECASTDLL_SOURCES})
if ( WIN32 AND NOT CYGWIN )
target_compile_definitions (RecastDll PRIVATE DLL_EXPORTS)
endif ( )