add membench-NX - open source memory benchmark tool
also remove memtester, it doesnt really work
This commit is contained in:
54
Source/Membench-NX/source/aarch64-asm.h
Normal file
54
Source/Membench-NX/source/aarch64-asm.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright © 2016 Siarhei Siamashka <siarhei.siamashka@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __AARCH64_ASM_H__
|
||||
#define __AARCH64_ASM_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void aligned_block_read_ldp_x_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_read_ldp_q_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_copy_ldpstp_x_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_copy_ldpstp_q_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_copy_ld1st1_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
|
||||
void aligned_block_copy_ldpstp_q_pf32_l2strm_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_copy_ldpstp_q_pf64_l2strm_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_copy_ldpstp_q_pf32_l1keep_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_copy_ldpstp_q_pf64_l1keep_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
|
||||
void aligned_block_fill_stp_x_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_fill_stp_q_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
|
||||
void aligned_block_fill_stnp_x_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
void aligned_block_fill_stnp_q_aarch64(int64_t *__restrict dst, int64_t *__restrict src, int size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
207
Source/Membench-NX/source/aarch64-asm.s
Normal file
207
Source/Membench-NX/source/aarch64-asm.s
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright © 2016 Siarhei Siamashka <siarhei.siamashka@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef __aarch64__
|
||||
|
||||
.cpu cortex-a57+fp+simd
|
||||
.text
|
||||
.align 2
|
||||
|
||||
#define PREFETCH_DISTANCE 320
|
||||
|
||||
.macro asm_function function_name
|
||||
.global \function_name
|
||||
.type \function_name,%function
|
||||
.func \function_name
|
||||
\function_name:
|
||||
DST .req x0
|
||||
SRC .req x1
|
||||
SIZE .req x2
|
||||
.endm
|
||||
|
||||
asm_function aligned_block_read_ldp_x_aarch64
|
||||
0:
|
||||
ldp x3, x4, [DST, #(0 * 16)]
|
||||
ldp x5, x6, [DST, #(1 * 16)]
|
||||
ldp x7, x8, [DST, #(2 * 16)]
|
||||
ldp x9, x10, [DST, #(3 * 16)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_copy_ldpstp_x_aarch64
|
||||
0:
|
||||
ldp x3, x4, [SRC, #(0 * 16)]
|
||||
ldp x5, x6, [SRC, #(1 * 16)]
|
||||
ldp x7, x8, [SRC, #(2 * 16)]
|
||||
ldp x9, x10, [SRC, #(3 * 16)]
|
||||
add SRC, SRC, #64
|
||||
stp x3, x4, [DST, #(0 * 16)]
|
||||
stp x5, x6, [DST, #(1 * 16)]
|
||||
stp x7, x8, [DST, #(2 * 16)]
|
||||
stp x9, x10, [DST, #(3 * 16)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_read_ldp_q_aarch64
|
||||
0:
|
||||
ldp q0, q1, [DST, #(0 * 32)]
|
||||
ldp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_copy_ldpstp_q_aarch64
|
||||
0:
|
||||
ldp q0, q1, [SRC, #(0 * 32)]
|
||||
ldp q2, q3, [SRC, #(1 * 32)]
|
||||
add SRC, SRC, #64
|
||||
stp q0, q1, [DST, #(0 * 32)]
|
||||
stp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_copy_ldpstp_q_pf32_l2strm_aarch64
|
||||
0:
|
||||
prfm pldl2strm, [SRC, #(PREFETCH_DISTANCE + 0)]
|
||||
ldp q0, q1, [SRC, #(0 * 32)]
|
||||
prfm pldl2strm, [SRC, #(PREFETCH_DISTANCE + 32)]
|
||||
ldp q2, q3, [SRC, #(1 * 32)]
|
||||
add SRC, SRC, #64
|
||||
stp q0, q1, [DST, #(0 * 32)]
|
||||
stp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_copy_ldpstp_q_pf64_l2strm_aarch64
|
||||
0:
|
||||
prfm pldl2strm, [SRC, #(PREFETCH_DISTANCE)]
|
||||
ldp q0, q1, [SRC, #(0 * 32)]
|
||||
ldp q2, q3, [SRC, #(1 * 32)]
|
||||
add SRC, SRC, #64
|
||||
stp q0, q1, [DST, #(0 * 32)]
|
||||
stp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_copy_ldpstp_q_pf32_l1keep_aarch64
|
||||
0:
|
||||
prfm pldl1keep, [SRC, #(PREFETCH_DISTANCE + 0)]
|
||||
ldp q0, q1, [SRC, #(0 * 32)]
|
||||
prfm pldl1keep, [SRC, #(PREFETCH_DISTANCE + 32)]
|
||||
ldp q2, q3, [SRC, #(1 * 32)]
|
||||
add SRC, SRC, #64
|
||||
stp q0, q1, [DST, #(0 * 32)]
|
||||
stp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_copy_ldpstp_q_pf64_l1keep_aarch64
|
||||
0:
|
||||
prfm pldl1keep, [SRC, #(PREFETCH_DISTANCE)]
|
||||
ldp q0, q1, [SRC, #(0 * 32)]
|
||||
ldp q2, q3, [SRC, #(1 * 32)]
|
||||
add SRC, SRC, #64
|
||||
stp q0, q1, [DST, #(0 * 32)]
|
||||
stp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_fill_stp_x_aarch64
|
||||
0:
|
||||
stp x3, x4, [DST, #(0 * 16)]
|
||||
stp x5, x6, [DST, #(1 * 16)]
|
||||
stp x7, x8, [DST, #(2 * 16)]
|
||||
stp x9, x10, [DST, #(3 * 16)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_fill_stp_q_aarch64
|
||||
0:
|
||||
stp q0, q1, [DST, #(0 * 32)]
|
||||
stp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_fill_stnp_x_aarch64
|
||||
0:
|
||||
stnp x3, x4, [DST, #(0 * 16)]
|
||||
stnp x5, x6, [DST, #(1 * 16)]
|
||||
stnp x7, x8, [DST, #(2 * 16)]
|
||||
stnp x9, x10, [DST, #(3 * 16)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_fill_stnp_q_aarch64
|
||||
0:
|
||||
stnp q0, q1, [DST, #(0 * 32)]
|
||||
stnp q2, q3, [DST, #(1 * 32)]
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
asm_function aligned_block_copy_ld1st1_aarch64
|
||||
0:
|
||||
ld1 {v0.16b, v1.16b, v2.16b, v3.16b}, [SRC]
|
||||
st1 {v0.16b, v1.16b, v2.16b, v3.16b}, [DST]
|
||||
add SRC, SRC, #64
|
||||
add DST, DST, #64
|
||||
subs SIZE, SIZE, #64
|
||||
bgt 0b
|
||||
ret
|
||||
.endfunc
|
||||
|
||||
#endif
|
||||
263
Source/Membench-NX/source/gpu_bw.c
Normal file
263
Source/Membench-NX/source/gpu_bw.c
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* gpu_bw.c - GPU bandwidth benchmark
|
||||
* Mirrors FUN_71000667e0 / FUN_7100056130 from decompiled binary exactly.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <switch.h>
|
||||
|
||||
#include "gpu_bw.h"
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <GLES3/gl31.h>
|
||||
|
||||
static EGLDisplay s_display = EGL_NO_DISPLAY;
|
||||
static EGLContext s_context = EGL_NO_CONTEXT;
|
||||
|
||||
static bool egl_init(void) {
|
||||
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (s_display == EGL_NO_DISPLAY) {
|
||||
printf("EGL: no display\n");
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
if (!eglInitialize(s_display, NULL, NULL)) {
|
||||
printf("EGL: initialize failed (0x%x)\n", eglGetError());
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
if (!eglBindAPI(EGL_OPENGL_API)) {
|
||||
printf("EGL: bindAPI(OPENGL) failed (0x%x)\n", eglGetError());
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *exts = eglQueryString(s_display, EGL_EXTENSIONS);
|
||||
if (!exts || !strstr(exts, "EGL_KHR_surfaceless_context")) {
|
||||
printf("EGL: no surfaceless_context\n");
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const EGLint cfg_attribs[] = {
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_BIT,
|
||||
EGL_RED_SIZE,
|
||||
8,
|
||||
EGL_GREEN_SIZE,
|
||||
8,
|
||||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_ALPHA_SIZE,
|
||||
8,
|
||||
EGL_DEPTH_SIZE,
|
||||
24,
|
||||
EGL_STENCIL_SIZE,
|
||||
8,
|
||||
EGL_NONE,
|
||||
};
|
||||
EGLConfig cfg;
|
||||
EGLint n;
|
||||
if (!eglChooseConfig(s_display, cfg_attribs, &cfg, 1, &n) || !n) {
|
||||
printf("EGL: chooseConfig failed n=%d (0x%x)\n", (int)n, eglGetError());
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const EGLint ctx_attribs[] = {
|
||||
EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_NONE,
|
||||
};
|
||||
s_context = eglCreateContext(s_display, cfg, EGL_NO_CONTEXT, ctx_attribs);
|
||||
if (s_context == EGL_NO_CONTEXT) {
|
||||
printf("EGL: createContext failed (0x%x)\n", eglGetError());
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, s_context) != EGL_TRUE) {
|
||||
printf("EGL: makeCurrent failed (0x%x)\n", eglGetError());
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_exit(void) {
|
||||
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
if (s_context != EGL_NO_CONTEXT) {
|
||||
eglDestroyContext(s_display, s_context);
|
||||
s_context = EGL_NO_CONTEXT;
|
||||
}
|
||||
if (s_display != EGL_NO_DISPLAY) {
|
||||
eglTerminate(s_display);
|
||||
s_display = EGL_NO_DISPLAY;
|
||||
}
|
||||
}
|
||||
|
||||
static GLuint compile_compute(const char *src, const char *name) {
|
||||
GLuint sh = glCreateShader(GL_COMPUTE_SHADER);
|
||||
glShaderSource(sh, 1, &src, NULL);
|
||||
glCompileShader(sh);
|
||||
GLint ok = 0;
|
||||
glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
|
||||
if (!ok) {
|
||||
char log[256] = { 0 };
|
||||
glGetShaderInfoLog(sh, sizeof(log), NULL, log);
|
||||
printf("Compute shader compile failed: %s\n", log[0] ? log : name);
|
||||
glDeleteShader(sh);
|
||||
return 0;
|
||||
}
|
||||
GLuint prog = glCreateProgram();
|
||||
glAttachShader(prog, sh);
|
||||
glLinkProgram(prog);
|
||||
glDeleteShader(sh);
|
||||
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
||||
if (!ok) {
|
||||
glDeleteProgram(prog);
|
||||
return 0;
|
||||
}
|
||||
return prog;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mirrors FUN_7100056130:
|
||||
* - one warmup dispatch + glFinish (before timing)
|
||||
* - `loops` timed dispatches + glFinish
|
||||
* - cntpct_el0 timing (19.2 MHz, ticks * 625/12/1e9 = seconds)
|
||||
* - returns (buf_bytes * loops) / elapsed / 1e6 [MB/s]
|
||||
*/
|
||||
static double run_pass(GLuint prog, GLuint ssbo_src, GLuint ssbo_dst, size_t buf_bytes, int loops) {
|
||||
GLuint groups = (GLuint)(buf_bytes >> 10);
|
||||
|
||||
glUseProgram(prog);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo_src);
|
||||
if (ssbo_dst)
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo_dst);
|
||||
|
||||
glDispatchCompute(groups, 1, 1);
|
||||
glFinish();
|
||||
if (glGetError() != GL_NO_ERROR)
|
||||
return 0.0;
|
||||
|
||||
uint64_t t0, t1;
|
||||
asm volatile("mrs %0, cntpct_el0" : "=r"(t0));
|
||||
for (int i = 0; i < loops; i++)
|
||||
glDispatchCompute(groups, 1, 1);
|
||||
glFinish();
|
||||
asm volatile("mrs %0, cntpct_el0" : "=r"(t1));
|
||||
|
||||
if (glGetError() != GL_NO_ERROR)
|
||||
return 0.0;
|
||||
|
||||
double elapsed = (double)(t1 - t0) * 625.0 / 12.0 / 1000000000.0;
|
||||
if (elapsed <= 0.0)
|
||||
return 0.0;
|
||||
return ((double)buf_bytes * (double)loops) / elapsed / 1000000.0;
|
||||
}
|
||||
|
||||
bool gpu_bw_run(bool is_4gb, double *copy_out, double *read_out, double *write_out) {
|
||||
/* Exact GLSL sources from binary — desktop GL 4.3 */
|
||||
static const char *src_copy = "\n#version 430\n"
|
||||
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
|
||||
"layout(std430, binding = 1) buffer dstBuffer { volatile uint dst[]; };\n"
|
||||
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
|
||||
"void main() {\n"
|
||||
" dst[gl_GlobalInvocationID.x] = src[gl_GlobalInvocationID.x];\n"
|
||||
"}\n";
|
||||
|
||||
static const char *src_read = "\n#version 430\n"
|
||||
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
|
||||
"shared uint tmp;\n"
|
||||
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
|
||||
"void main() {\n"
|
||||
" tmp |= src[gl_GlobalInvocationID.x];\n"
|
||||
"}\n";
|
||||
|
||||
static const char *src_write = "\n#version 430\n"
|
||||
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
|
||||
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
|
||||
"void main() {\n"
|
||||
" src[gl_GlobalInvocationID.x] = 0xAAAAAAAAu;\n"
|
||||
"}\n";
|
||||
|
||||
if (!egl_init()) {
|
||||
printf("Failed to initialize EGL/GL for GPU benchmark.\n");
|
||||
consoleUpdate(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
GLint max_ssbo = 0;
|
||||
glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo);
|
||||
if (max_ssbo < 1) {
|
||||
printf("Failed to query GPU SSBO size.\n");
|
||||
consoleUpdate(NULL);
|
||||
egl_exit();
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t buf_bytes = (size_t)max_ssbo;
|
||||
if (buf_bytes > 0x8000000)
|
||||
buf_bytes = 0x8000000;
|
||||
if (!is_4gb)
|
||||
buf_bytes >>= 1;
|
||||
buf_bytes &= ~(size_t)0x3FF;
|
||||
|
||||
if (buf_bytes < 0x400) {
|
||||
printf("GPU benchmark buffer is too small.\n");
|
||||
consoleUpdate(NULL);
|
||||
egl_exit();
|
||||
return false;
|
||||
}
|
||||
|
||||
int loops = is_4gb ? 400 : 800;
|
||||
int wloops = loops / 10;
|
||||
|
||||
GLuint prog_copy = compile_compute(src_copy, "GPU Copy");
|
||||
GLuint prog_read = compile_compute(src_read, "GPU Read");
|
||||
GLuint prog_write = compile_compute(src_write, "GPU Write");
|
||||
if (!prog_copy || !prog_read || !prog_write) {
|
||||
if (prog_copy)
|
||||
glDeleteProgram(prog_copy);
|
||||
if (prog_read)
|
||||
glDeleteProgram(prog_read);
|
||||
if (prog_write)
|
||||
glDeleteProgram(prog_write);
|
||||
egl_exit();
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint ssbo[2] = { 0, 0 };
|
||||
glGenBuffers(2, ssbo);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[0]);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)buf_bytes, NULL, GL_DYNAMIC_COPY);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[1]);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)buf_bytes, NULL, GL_DYNAMIC_COPY);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||
if (glGetError() != GL_NO_ERROR) {
|
||||
printf("Failed to allocate GPU buffers!\n");
|
||||
consoleUpdate(NULL);
|
||||
glDeleteBuffers(2, ssbo);
|
||||
glDeleteProgram(prog_copy);
|
||||
glDeleteProgram(prog_read);
|
||||
glDeleteProgram(prog_write);
|
||||
egl_exit();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Pass order mirrors binary: warmup write+read, timed copy/read/write */
|
||||
run_pass(prog_write, ssbo[0], 0, buf_bytes, wloops);
|
||||
run_pass(prog_read, ssbo[0], 0, buf_bytes, wloops);
|
||||
|
||||
*copy_out = run_pass(prog_copy, ssbo[0], ssbo[1], buf_bytes, loops);
|
||||
*read_out = run_pass(prog_read, ssbo[0], 0, buf_bytes, loops);
|
||||
*write_out = run_pass(prog_write, ssbo[0], 0, buf_bytes, loops);
|
||||
|
||||
glDeleteBuffers(2, ssbo);
|
||||
glDeleteProgram(prog_copy);
|
||||
glDeleteProgram(prog_read);
|
||||
glDeleteProgram(prog_write);
|
||||
egl_exit();
|
||||
return true;
|
||||
}
|
||||
4
Source/Membench-NX/source/gpu_bw.h
Normal file
4
Source/Membench-NX/source/gpu_bw.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gpu_bw_run(bool is_4gb, double *copy_out, double *read_out, double *write_out);
|
||||
422
Source/Membench-NX/source/main.c
Normal file
422
Source/Membench-NX/source/main.c
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* Copyright © 2011 Siarhei Siamashka <siarhei.siamashka@gmail.com>
|
||||
* Copyright (c) 20xx KazushiMe
|
||||
* Copyright (c) 2025 Souldbminer
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <semaphore.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#define SIZE (32 * 1024 * 1024)
|
||||
#ifndef MAXREPEATS
|
||||
#define MAXREPEATS 10
|
||||
#endif
|
||||
#ifndef LATBENCH_COUNT
|
||||
#define LATBENCH_COUNT 10000000
|
||||
#endif
|
||||
#define ALIGN_PADDING 0x100000
|
||||
#define CACHE_LINE_SIZE 128
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#include "gpu_bw.h"
|
||||
|
||||
#define YEL "\033[1;92m"
|
||||
#define GRN "\033[1;92m"
|
||||
#define RST "\033[0m"
|
||||
|
||||
PadState pad;
|
||||
|
||||
// Threads
|
||||
|
||||
struct f_data {
|
||||
void (*func)(int64_t *, int64_t *, int);
|
||||
int64_t *arg1;
|
||||
int64_t *arg2;
|
||||
int arg3;
|
||||
};
|
||||
|
||||
pthread_cond_t p_ready, p_start;
|
||||
pthread_mutex_t p_lock;
|
||||
pthread_t *p_worker = NULL;
|
||||
struct f_data *worker_data = NULL;
|
||||
int p_worker_not_ready, p_workers_ready;
|
||||
|
||||
void *thread_func(void *data) {
|
||||
struct f_data *d = data;
|
||||
pthread_mutex_lock(&p_lock);
|
||||
p_worker_not_ready--;
|
||||
if (!p_worker_not_ready)
|
||||
pthread_cond_signal(&p_ready);
|
||||
while (p_workers_ready != 1)
|
||||
pthread_cond_wait(&p_start, &p_lock);
|
||||
pthread_mutex_unlock(&p_lock);
|
||||
(d->func)(d->arg1, d->arg2, d->arg3);
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
static void parallel_run(void) {
|
||||
pthread_mutex_lock(&p_lock);
|
||||
p_workers_ready = 1;
|
||||
pthread_mutex_unlock(&p_lock);
|
||||
pthread_cond_broadcast(&p_start);
|
||||
}
|
||||
|
||||
static void parallel_init(int threads) {
|
||||
pthread_attr_t attr;
|
||||
pthread_cond_init(&p_ready, NULL);
|
||||
pthread_cond_init(&p_start, NULL);
|
||||
pthread_mutex_init(&p_lock, NULL);
|
||||
p_worker_not_ready = threads;
|
||||
p_workers_ready = 0;
|
||||
pthread_attr_init(&attr);
|
||||
if (!p_worker || !worker_data) {
|
||||
p_worker = malloc(threads * sizeof(pthread_t));
|
||||
worker_data = malloc(threads * sizeof(struct f_data));
|
||||
}
|
||||
for (int i = 0; i < threads; i++)
|
||||
pthread_create(p_worker + i, &attr, thread_func, worker_data + i);
|
||||
pthread_mutex_lock(&p_lock);
|
||||
while (p_worker_not_ready != 0)
|
||||
pthread_cond_wait(&p_ready, &p_lock);
|
||||
pthread_mutex_unlock(&p_lock);
|
||||
}
|
||||
|
||||
// memops
|
||||
|
||||
void aligned_block_copy(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
|
||||
volatile int64_t *dst = dst_;
|
||||
int64_t t1, t2, t3, t4;
|
||||
while ((size -= 64) >= 0) {
|
||||
t1 = *src++;
|
||||
t2 = *src++;
|
||||
t3 = *src++;
|
||||
t4 = *src++;
|
||||
*dst++ = t1;
|
||||
*dst++ = t2;
|
||||
*dst++ = t3;
|
||||
*dst++ = t4;
|
||||
t1 = *src++;
|
||||
t2 = *src++;
|
||||
t3 = *src++;
|
||||
t4 = *src++;
|
||||
*dst++ = t1;
|
||||
*dst++ = t2;
|
||||
*dst++ = t3;
|
||||
*dst++ = t4;
|
||||
}
|
||||
}
|
||||
|
||||
void aligned_block_fetch(int64_t *__restrict dst, int64_t *__restrict src_, int size) {
|
||||
volatile int64_t *src = src_;
|
||||
(void)dst;
|
||||
while ((size -= 64) >= 0) {
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
}
|
||||
}
|
||||
|
||||
void aligned_block_fill(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
|
||||
volatile int64_t *dst = dst_;
|
||||
int64_t data = *src;
|
||||
while ((size -= 64) >= 0) {
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
}
|
||||
}
|
||||
|
||||
double gettime(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (double)((int64_t)tv.tv_sec * 1000000 + tv.tv_usec) / 1000000.;
|
||||
}
|
||||
|
||||
static double bandwidth_bench_helper(int threads, int64_t *dstbuf, int64_t *srcbuf, int size, void (*f)(int64_t *, int64_t *, int)) {
|
||||
int i, loopcount, innerloopcount, n;
|
||||
double t, t1, t2, speed, maxspeed, s, s0, s1, s2;
|
||||
|
||||
s = s0 = s1 = s2 = 0.;
|
||||
maxspeed = 0.;
|
||||
for (n = 0; n < MAXREPEATS; n++) {
|
||||
loopcount = 0;
|
||||
innerloopcount = 1;
|
||||
t = 0.;
|
||||
do {
|
||||
loopcount += innerloopcount;
|
||||
for (i = 0; i < innerloopcount; i++) {
|
||||
parallel_init(threads);
|
||||
for (int pt = 0; pt < threads; pt++) {
|
||||
(worker_data + pt)->func = f;
|
||||
(worker_data + pt)->arg1 = dstbuf + size * pt / sizeof(int64_t);
|
||||
(worker_data + pt)->arg2 = srcbuf + size * pt / sizeof(int64_t);
|
||||
(worker_data + pt)->arg3 = size;
|
||||
}
|
||||
t1 = gettime();
|
||||
parallel_run();
|
||||
for (int pt = 0; pt < threads; pt++)
|
||||
pthread_join(p_worker[pt], NULL);
|
||||
t2 = gettime();
|
||||
t += t2 - t1;
|
||||
}
|
||||
innerloopcount *= 2;
|
||||
} while (t < 0.5);
|
||||
|
||||
speed = (double)size * threads * loopcount / t / 1000000.;
|
||||
s0 += 1.;
|
||||
s1 += speed;
|
||||
s2 += speed * speed;
|
||||
if (speed > maxspeed)
|
||||
maxspeed = speed;
|
||||
if (s0 > 2.) {
|
||||
s = sqrt((s0 * s2 - s1 * s1) / (s0 * (s0 - 1)));
|
||||
if (s < maxspeed / 1000.)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return maxspeed;
|
||||
}
|
||||
|
||||
static char *align_up(char *ptr, int align) {
|
||||
return (char *)(((uintptr_t)ptr + align - 1) & ~(uintptr_t)(align - 1));
|
||||
}
|
||||
|
||||
void *alloc_nonaliased_buffers(void **buf1_, int size1, void **buf2_, int size2, void **buf3_, int size3) {
|
||||
char **buf1 = (char **)buf1_, **buf2 = (char **)buf2_, **buf3 = (char **)buf3_;
|
||||
int mask = (ALIGN_PADDING - 1) & ~(CACHE_LINE_SIZE - 1);
|
||||
char *buf = malloc(size1 + size2 + size3 + 9 * ALIGN_PADDING);
|
||||
char *ptr = buf;
|
||||
memset(buf, 0xCC, size1 + size2 + size3 + 9 * ALIGN_PADDING);
|
||||
ptr = align_up(ptr, ALIGN_PADDING);
|
||||
if (buf1) {
|
||||
*buf1 = ptr + (0xAAAAAAAA & mask);
|
||||
ptr = align_up(*buf1 + size1, ALIGN_PADDING);
|
||||
}
|
||||
if (buf2) {
|
||||
*buf2 = ptr + (0x55555555 & mask);
|
||||
ptr = align_up(*buf2 + size2, ALIGN_PADDING);
|
||||
}
|
||||
if (buf3) {
|
||||
*buf3 = ptr + (0xCCCCCCCC & mask);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
static void __attribute__((noinline)) random_read_test(char *buf, int count, int nbits) {
|
||||
uint32_t seed = 0;
|
||||
uintptr_t mask = (1 << nbits) - 1;
|
||||
uint32_t v;
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
static volatile uint32_t dummy;
|
||||
#define RMA() \
|
||||
seed = seed * 1103515245 + 12345; \
|
||||
v = (seed >> 16) & 0xFF; \
|
||||
seed = seed * 1103515245 + 12345; \
|
||||
v |= (seed >> 8) & 0xFF00; \
|
||||
seed = seed * 1103515245 + 12345; \
|
||||
v |= seed & 0x7FFF0000; \
|
||||
seed |= buf[v & mask];
|
||||
while (count >= 16) {
|
||||
RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() count -= 16;
|
||||
}
|
||||
dummy = seed;
|
||||
#undef RMA
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
static double latency_measure(char *buf, int nbits, int count) {
|
||||
double t_noaccess = 0, t_before, t_after, t, xs1 = 0, xs2 = 0, min_t = 0;
|
||||
double xs;
|
||||
int n;
|
||||
|
||||
for (n = 1; n <= MAXREPEATS; n++) {
|
||||
t_before = gettime();
|
||||
random_read_test(buf, count, 1);
|
||||
t_after = gettime();
|
||||
if (n == 1 || t_after - t_before < t_noaccess)
|
||||
t_noaccess = t_after - t_before;
|
||||
}
|
||||
|
||||
for (n = 1; n <= MAXREPEATS; n++) {
|
||||
t_before = gettime();
|
||||
random_read_test(buf, count, nbits);
|
||||
t_after = gettime();
|
||||
t = t_after - t_before - t_noaccess;
|
||||
if (t < 0)
|
||||
t = 0;
|
||||
xs1 += t;
|
||||
xs2 += t * t;
|
||||
if (n == 1 || t < min_t)
|
||||
min_t = t;
|
||||
if (n > 2) {
|
||||
xs = sqrt((xs2 * n - xs1 * xs1) / (n * (n - 1)));
|
||||
if (xs < min_t / 1000.)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return min_t * 1000000000.0 / count;
|
||||
}
|
||||
|
||||
static void latency_bench(double *l2_out, double *ram_out) {
|
||||
char *buf_alloc = malloc(0x2001000);
|
||||
char *buf = (char *)(((uintptr_t)buf_alloc + 4095) & ~(uintptr_t)4095);
|
||||
memset(buf, 0, 0x2000000);
|
||||
|
||||
/* nbits=20 (1MB) for L2, nbits=25 (32MB) for RAM — from binary assembly */
|
||||
*l2_out = latency_measure(buf, 20, LATBENCH_COUNT);
|
||||
*ram_out = latency_measure(buf, 25, LATBENCH_COUNT);
|
||||
|
||||
free(buf_alloc);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
consoleInit(NULL);
|
||||
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
||||
padInitializeDefault(&pad);
|
||||
|
||||
const int threads = 3;
|
||||
const int size = SIZE;
|
||||
bool is_4gb = (appletGetAppletType() == AppletType_Application);
|
||||
|
||||
loop:
|
||||
printf("Membench-NX\n\n");
|
||||
printf("Press A to run all benchmarks.\n");
|
||||
printf("Press any other key to exit.\n\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
while (appletMainLoop()) {
|
||||
padUpdate(&pad);
|
||||
u64 kDown = padGetButtonsDown(&pad);
|
||||
if (kDown & HidNpadButton_A)
|
||||
break;
|
||||
if (kDown) {
|
||||
consoleExit(NULL);
|
||||
return 0;
|
||||
}
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
appletSetAutoSleepDisabled(true);
|
||||
consoleClear();
|
||||
|
||||
uint32_t cpu_hz = 0, gpu_hz = 0, mem_hz = 0;
|
||||
if (R_SUCCEEDED(clkrstInitialize())) {
|
||||
ClkrstSession s;
|
||||
clkrstOpenSession(&s, PcvModuleId_CpuBus, 3);
|
||||
clkrstGetClockRate(&s, &cpu_hz);
|
||||
clkrstCloseSession(&s);
|
||||
clkrstOpenSession(&s, PcvModuleId_GPU, 3);
|
||||
clkrstGetClockRate(&s, &gpu_hz);
|
||||
clkrstCloseSession(&s);
|
||||
clkrstOpenSession(&s, PcvModuleId_EMC, 3);
|
||||
clkrstGetClockRate(&s, &mem_hz);
|
||||
clkrstCloseSession(&s);
|
||||
clkrstExit();
|
||||
}
|
||||
|
||||
printf("Membench-NX\n\n");
|
||||
printf("----------------------------\n");
|
||||
printf("CPU: %.1f MHz\n", cpu_hz / 1000000.0);
|
||||
printf("GPU: %.1f MHz\n", gpu_hz / 1000000.0);
|
||||
printf("MEM: %.1f MHz\n", mem_hz / 1000000.0);
|
||||
printf("Mode: " GRN "%s" RST "\n", is_4gb ? "Application" : "Applet");
|
||||
printf("Threads: %d (max: %d)\n", threads, threads);
|
||||
printf("----------------------------\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
printf("\nStarted Bandwidth benchmark via GPU...\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
double gpu_copy = 0, gpu_read = 0, gpu_write = 0;
|
||||
gpu_bw_run(is_4gb, &gpu_copy, &gpu_read, &gpu_write);
|
||||
|
||||
printf(" " YEL "%-40s" RST " : %8.1f MB/s\n", "GPU Copy", gpu_copy);
|
||||
printf(" " YEL "%-40s" RST " : %8.1f MB/s\n", "GPU Read", gpu_read);
|
||||
printf(" " YEL "%-40s" RST " : %8.1f MB/s\n", "GPU Write", gpu_write);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
int64_t *srcbuf, *dstbuf;
|
||||
void *poolbuf = alloc_nonaliased_buffers((void **)&srcbuf, size * threads, (void **)&dstbuf, size * threads, NULL, 0);
|
||||
|
||||
printf("\nStarted Bandwidth benchmark with %d threads...\n", threads);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
double cpu_copy = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_copy);
|
||||
double cpu_read = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fetch);
|
||||
double cpu_write = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fill);
|
||||
|
||||
printf(" " YEL "%-40s" RST " : %8.1f MB/s\n", "CPU Copy", cpu_copy);
|
||||
printf(" " YEL "%-40s" RST " : %8.1f MB/s\n", "CPU Read", cpu_read);
|
||||
printf(" " YEL "%-40s" RST " : %8.1f MB/s\n", "CPU Write", cpu_write);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
free(poolbuf);
|
||||
|
||||
printf("\nStarted Latency benchmark with 1 thread...\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
double l2_ns = 0, ram_ns = 0;
|
||||
latency_bench(&l2_ns, &ram_ns);
|
||||
|
||||
printf(" " YEL "%-40s" RST " : %8.1f ns\n", "L2", l2_ns);
|
||||
printf(" " YEL "%-40s" RST " : %8.1f ns\n", "Full RAM", ram_ns);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
appletSetAutoSleepDisabled(false);
|
||||
|
||||
printf("\nPress A to continue, any other key to exit.\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
while (appletMainLoop()) {
|
||||
padUpdate(&pad);
|
||||
u64 kDown = padGetButtonsDown(&pad);
|
||||
if (kDown & HidNpadButton_A) {
|
||||
consoleClear();
|
||||
goto loop;
|
||||
}
|
||||
if (kDown)
|
||||
break;
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
consoleExit(NULL);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user