Hazel游戏引擎(100)Vulkan、SPIR-V和新Shader系统

2023-10-13 04:20

本文主要是介绍Hazel游戏引擎(100)Vulkan、SPIR-V和新Shader系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文中若有代码、术语等错误,欢迎指正

文章目录

  • 前言
  • 介绍SPIR-V
  • 项目改变
  • 代码流程
    • 给GPU的Uniform缓冲区上传数据
    • SPIR-V编译
  • 项目遇到耗费我一天半的BUG
    • BUG信息以及解决方法
    • 我解决此BUG的路线
  • 运行脚本安装Vulkan遇到的问题

前言

  • 前前言

    • Cherno未来想做的

      当前项目以后Cherno打算支持vulkan,由于vulkan着色器代码也支持glsl语言,但是和Opengl的glsl标准不一

      因为Vulkan中存在OpenGL的不存在的东西(反之亦然),所以着色器代码肯定不同。

    • vulkan和opengl的glsl对比-以Uniform为例

      • 主要不同

        在于,opengl支持uniform,vulkan不支持uniform,而是支持uniform缓冲区(push_constant、存储缓冲区)这比OpenGL的glsl更好。

        opengl的uniform

        uniform mat4 m_transform;
        

        vulkan的uniform缓冲区

        layout(std140, binding=2) uniform Transform{mat4 Transform;
        }
        
      • Opengl支持的uniform哪里不好

        场景有很多个物体,这样一个物体调用一次drawcall(不是批处理模式),每个物体需要显示都需要上传摄像机的投影视图矩阵,那么10000个物体就有10000个uniform更新,但其实摄像机的投影视图矩阵在当前帧不变的,这样就会造成性能的下降。

      • vulkan哪里好

        vulkan的uniform是在GPU开辟的一块缓冲区,每个物体需要摄像机的投影视图矩阵,只需将这块缓冲区放入投影矩阵的值,然后每个drawcall都去访问这块缓冲区,得到投影视图矩阵就行,性能更好。

      • 但后面我发现,OpenGL也有uniform缓冲区,不知是否我理解错了。

  • 此节目的

    重新写shader系统,使其能支持vulkan和opengl两种glsl

  • 如何实现

    使用SPIR-V,作为中间表示语言,即可支持vulkan也支持opengl的glsl

介绍SPIR-V

  • 介绍SPIR-V

    vulkanApi要求以SPIR-V组件的形式提供着色器,而这个SPIR-V相当于“中间表示语言

    1. 可以将写好的glsl、hlsl转换为SPIR-V
    2. 也可以将SPIR-V转换为glsl、hlsl。

    使得可以完成只写次shader,自动生成各种不同版本的shader语言

  • SPIR-V什么工作方式:(可能我理解错了了)

    Cherno说,将vulkan的glsl用SPIR-V编译成SPIR-V二进制文件,然后可以用SPIR-V的交叉编译这个二进制文件可以成hlsl、metal,以及兼容OpenGL的glsl。

  • 实现过程中的重要功能

    使用SPIR-V加入着色器缓存功能,不用每次都编译着色器,节省时间

以上很有可能说的不正确,我有点迷糊,东拼西凑的写完以上内容,但大概意思是这样。

项目改变

  • 下载vulkan

    地址

    需要下载最新,安装的时候每个组件最好都点上,需要有这几个文件

  • 环境变量

    请添加图片描述

    特别是VULKAN_SDK,接下来的premake会获取这个环境变量

  • 修改premake

    ......
    outputdir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}"VULKAN_SDK = os.getenv("VULKAN_SDK")-- Include directories relative to root folder (solution directory)
    IncludeDir = {}
    IncludeDir["GLFW"] = "GameEngineLightWeight/vendor/GLFW/include"
    ......
    IncludeDir["ImGuizmo"] = "GameEngineLightWeight/vendor/ImGuizmo" 
    IncludeDir["VulkanSDK"] = "%{VULKAN_SDK}/Include"LibraryDir = {}LibraryDir["VulkanSDK"] = "%{VULKAN_SDK}/Lib"
    LibraryDir["VulkanSDK_Debug"] = "%{wks.location}/GameEngineLightWeight/vendor/VulkanSDK/Lib"Library = {}
    Library["Vulkan"] = "%{LibraryDir.VulkanSDK}/vulkan-1.lib"
    Library["VulkanUtils"] = "%{LibraryDir.VulkanSDK}/VkLayer_utils.lib"Library["ShaderC_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/shaderc_sharedd.lib"
    Library["SPIRV_Cross_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/spirv-cross-cored.lib"
    Library["SPIRV_Cross_GLSL_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/spirv-cross-glsld.lib"
    Library["SPIRV_Tools_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/SPIRV-Toolsd.lib"Library["ShaderC_Release"] = "%{LibraryDir.VulkanSDK}/shaderc_shared.lib"
    Library["SPIRV_Cross_Release"] = "%{LibraryDir.VulkanSDK}/spirv-cross-core.lib"
    Library["SPIRV_Cross_GLSL_Release"] = "%{LibraryDir.VulkanSDK}/spirv-cross-glsl.lib"group "Dependencies"include "GameEngineLightWeight/vendor/GLFW"
    ......
    project "GameEngineLightWeight"location "GameEngineLightWeight"includedirs{......"%{IncludeDir.ImGuizmo}","%{IncludeDir.VulkanSDK}"}......filter "configurations:Debug"......links{"%{Library.ShaderC_Debug}","%{Library.SPIRV_Cross_Debug}","%{Library.SPIRV_Cross_GLSL_Debug}"}filter "configurations:Release"......links{"%{Library.ShaderC_Release}","%{Library.SPIRV_Cross_Release}","%{Library.SPIRV_Cross_GLSL_Release}"}
    

    注意,项目要取消动态链接改为静态链接了,可能是vulkan的那几个lib文件是静态的

    kind "StaticLib"
    -- staticruntime "on"
    staticruntime "off"
    

代码流程

给GPU的Uniform缓冲区上传数据

  • 写vulkan的glsl

    // 纹理的glsl
    #type vertex
    #version 450 corelayout(location = 0) in vec3 a_Position;
    layout(location = 1) in vec4 a_Color;
    layout(location = 2) in vec2 a_TexCoord;
    layout(location = 3) in float a_TexIndex;
    layout(location = 4) in float a_TilingFactor;
    layout(location = 5) in int a_EntityID;//
    // uniform缓冲区///
    // 使用0号缓冲区///
    layout(std140, binding = 0) uniform Camera{ // std140是布局mat4 u_ViewProjection;
    };
    // uniform mat4 u_ViewProjection;
    struct VertexOutput {vec4 Color;vec2 TexCoord;float TexIndex;float TilingFactor;
    };
    layout(location = 0) out VertexOutput Output;
    layout(location = 4) out flat int v_EntityID; // 4 是因为TilingFactor 是3
    //out vec4 v_Color;
    //out vec2 v_TexCoord;
    //out float v_TexIndex;
    //out float v_TilingFactor;
    //out flat int v_EntityId;void main() {/*v_Color = a_Color;v_TexCoord = a_TexCoord;v_TexIndex = a_TexIndex;v_TilingFactor = a_TilingFactor;v_EntityId = a_EntityId;*/Output.Color = a_Color;Output.TexCoord = a_TexCoord;Output.TexIndex = a_TexIndex;Output.TilingFactor = a_TilingFactor;v_EntityID = a_EntityID;gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
    }#type fragment
    #version 450 corelayout(location = 0) out vec4 color;
    layout(location = 1) out int color2;struct VertexOutput {vec4 Color;vec2 TexCoord;float TexIndex;float TilingFactor;
    };
    layout(location = 0) in VertexOutput Input;
    layout(location = 4) in flat int v_EntityID; // 4 是因为TilingFactor 是3//in vec4 v_Color;
    //in vec2 v_TexCoord;
    //in float v_TexIndex;
    //in float v_TilingFactor;
    //in flat int v_EntityId;layout(binding = 0) uniform sampler2D u_Textures[32];
    //uniform sampler2D u_Textures[32];void main() {color = texture(u_Textures[int(Input.TexIndex)], Input.TexCoord * Input.TilingFactor) * Input.Color;color2 = v_EntityID;// 给顶点包围的区域每个像素都设置为实体ID
    }
    
    • std140是什么

      是一种布局,点这了解

    • 注意被注释的是OpenGL的Glsl写法

  • 新建UniformBuffer类

    #pragma once
    #include "Hazel/Core/Core.h"namespace Hazel {class UniformBuffer{public:virtual ~UniformBuffer(){}virtual void SetData(const void* data, uint32_t size, uint32_t offset = 0) = 0;static Ref<UniformBuffer> Create(uint32_t size, uint32_t binding);};
    }
    
    #include "hzpch.h"
    #include "OpenGLUniformBuffer.h"
    #include <glad/glad.h>namespace Hazel {//// Uniform缓冲区/OpenGLUniformBuffer::OpenGLUniformBuffer(uint32_t size, uint32_t binding){// GPU创建缓冲区,并且返回缓冲区号m_RendererIDglCreateBuffers(1, &m_RendererID);// 声明缓冲区的数据glNamedBufferData(m_RendererID, size, nullptr, GL_DYNAMIC_DRAW); // TODO:investigate usage hint// 将在glsl上设置的bingding 0号缓冲区与真正的缓冲区m_RendererID联系起来glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_RendererID);/*	 glsl代码中声明使用0号的uniform缓冲区layout(std140, binding = 0) uniform Camera*/}OpenGLUniformBuffer::~OpenGLUniformBuffer(){glDeleteBuffers(1, &m_RendererID);}// 上传数据给缓冲区///void OpenGLUniformBuffer::SetData(const void* data, uint32_t size, uint32_t offset){// 上传数据给m_RendererID号缓冲区吧,实则给GPU的bingding号缓冲区glNamedBufferSubData(m_RendererID, offset, size, data);}
    }

    由上,glBindBufferBase,将使glsl上设置的bingding 0号缓冲区与真正的缓冲区m_RendererID联系起来

  • Renderer2D.cpp上传摄像机的投影视图矩阵给Uniform缓冲区

    struct Renderer2DData
    {.....struct CameraData{glm::mat4 ViewProjection;};CameraData CameraBuffer;Ref<UniformBuffer> CameraUniformBuffer;
    };void Renderer2D::Init()
    {.....// 初始化CameraUniformBuffer实例,在构造函数中就将调用上面的glBindBufferBase函数s_Data.CameraUniformBuffer = UniformBuffer::Create(sizeof(Renderer2DData::CameraData), 0);
    }
    void Renderer2D::BeginScene(const EditorCamera& camera)
    {HZ_PROFILE_FUNCTION();s_Data.CameraBuffer.ViewProjection = camera.GetViewProjection();// 上传数据给缓冲区///// 使用CameraUniformBuffer的SetData才真正的上传投影视图矩阵给GPU的Uniform缓冲区s_Data.CameraUniformBuffer->SetData(&s_Data.CameraBuffer, sizeof(Renderer2DData::CameraData));StartBatch();
    }
    

    给CameraUniformBuffer的m_RendererID缓冲区上传数据,也是上传到glsl上声明使用0号uniform缓冲区上

    layout(std140, binding = 0) uniform Camera

SPIR-V编译

  • 将vulkan的glsl编译成SPIR-V二进制文件,并且保存在本地作为缓存

    void OpenGLShader::CompileOrGetVulkanBinaries(const std::unordered_map<GLenum, std::string>& shaderSources)
    {GLuint program = glCreateProgram();shaderc::Compiler compiler;shaderc::CompileOptions options;options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);const bool optimize = true;if (optimize) {// 优化:性能优先options.SetOptimizationLevel(shaderc_optimization_level_performance);}// 生成二进制的缓存目录std::filesystem::path cacheDirectory = Utils::GetCacheDirectory();auto& shaderData = m_VulkanSPIRV;shaderData.clear();for (auto&& [stage, source] : shaderSources){std::filesystem::path shaderFilePath = m_FilePath;std::filesystem::path cachedPath = cacheDirectory / (shaderFilePath.filename().string() + Utils::GLShaderStageCachedOpenGLFileExtension(stage));std::ifstream in(cachedPath, std::ios::in | std::ios::binary);// 缓存是否存在if (in.is_open()) {// 存在打开加载in.seekg(0, std::ios::end);auto size = in.tellg();in.seekg(0, std::ios::beg);auto& data = shaderData[stage];// ?data.resize(size / sizeof(uint32_t));in.read((char*)data.data(), size);}else {/// 将Vulkan的glsl编译成SPIR-V二进制文件shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), m_FilePath.c_str(), options);if (module.GetCompilationStatus() != shaderc_compilation_status_success) {HZ_CORE_ERROR(module.GetErrorMessage());HZ_CORE_ASSERT(false);}shaderData[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());std::ofstream out(cachedPath, std::ios::out | std::ios::binary);if (out.is_open()) {auto& data = shaderData[stage];out.write((char*)data.data(), data.size() * sizeof(uint32_t));out.flush();out.close();}}}for (auto&& [stage, data] : shaderData)Reflect(stage, data);
    }
    
  • 将Vulkan的glsl的SPIR-V二进制文件转换为OpenGL的glsl源文件字符串

    再将OpenGL的glsl源文件字符串转换为SPIR-V二进制文件,并且保存在本地作为缓存

    void OpenGLShader::CompileOrGetOpenGLBinaries()
    {auto& shaderData = m_OpenGLSPIRV;shaderc::Compiler compiler;shaderc::CompileOptions options;options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_3);const bool optimize = true;if (optimize) {// 优化:性能优先options.SetOptimizationLevel(shaderc_optimization_level_performance);}// 生成二进制的缓存目录std::filesystem::path cacheDirectory = Utils::GetCacheDirectory();shaderData.clear();m_OpenGLSourceCode.clear();for (auto&& [stage, spirv] : m_VulkanSPIRV){std::filesystem::path shaderFilePath = m_FilePath;std::filesystem::path cachedPath = cacheDirectory / (shaderFilePath.filename().string() + Utils::GLShaderStageCachedOpenGLFileExtension(stage));std::ifstream in(cachedPath, std::ios::in | std::ios::binary);// 缓存是否存在if (in.is_open()) {// 存在打开加载in.seekg(0, std::ios::end);auto size = in.tellg();in.seekg(0, std::ios::beg);auto& data = shaderData[stage];// ?data.resize(size / sizeof(uint32_t));in.read((char*)data.data(), size);}else {/// 将Vulkan的glsl的SPIR-V二进制文件转换为OpenGL的glsl源文件字符串spirv_cross::CompilerGLSL glslCompiler(spirv);m_OpenGLSourceCode[stage] = glslCompiler.compile();auto& source = m_OpenGLSourceCode[stage];/// 再将OpenGL的glsl源文件字符串转换为SPIR-V二进制文件,并且保存在本地作为缓存shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), m_FilePath.c_str(), options);if (module.GetCompilationStatus() != shaderc_compilation_status_success) {HZ_CORE_ERROR(module.GetErrorMessage());HZ_CORE_ASSERT(false);}shaderData[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());std::ofstream out(cachedPath, std::ios::out | std::ios::binary);if (out.is_open()) {auto& data = shaderData[stage];out.write((char*)data.data(), data.size() * sizeof(uint32_t));out.flush();out.close();}}}
    }
    
  • 用OpenGL的API编译链接OpenGL版本的glsl的SPIR-V二进制文件

    与原始编译链接OpenGL的GLSL着色器代码不太一样

    void OpenGLShader::CreateProgram()
    {GLuint program = glCreateProgram();std::vector<GLuint> shaderIDs;for (auto&& [stage, spirv] : m_OpenGLSPIRV){GLuint shaderID = shaderIDs.emplace_back(glCreateShader(stage));glShaderBinary(1, &shaderID, GL_SHADER_BINARY_FORMAT_SPIR_V, spirv.data(), spirv.size() * sizeof(uint32_t));glSpecializeShader(shaderID, "main", 0, nullptr, nullptr);glAttachShader(program, shaderID);}glLinkProgram(program);GLint isLinked;glGetProgramiv(program, GL_LINK_STATUS, &isLinked);if (isLinked == GL_FALSE){GLint maxLength;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);std::vector<GLchar> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, infoLog.data());HZ_CORE_ERROR("Shader linking failed ({0}):\n{1}", m_FilePath, infoLog.data());glDeleteProgram(program);for (auto id : shaderIDs)glDeleteShader(id);}for (auto id : shaderIDs){glDetachShader(program, id);glDeleteShader(id);}m_RendererID = program;
    }
    

项目遇到耗费我一天半的BUG

BUG信息以及解决方法

  • 错误如下

    0x00007FFE06F0FDB6 (atio6axx.dll)处(位于 GameEngine-Editor.exe 中)引发的异常: 0xC0000005: 写入位置 0x0000000000000008 时发生访问冲突。

  • 错误原因

    AMD显卡运行由OpenGL的SPIR-V二进制编译的shader会报这个错,是AMD显卡的问题!

  • 解决方法一

    显卡设置用英伟达显卡运行此程序解决

    请添加图片描述

    请添加图片描述

  • 解决方法二:适合只有AMD显卡的电脑

    来自Github上Hazel的ISSUE:点这

    解决方法来自ISSUE fixed:点这

    写openglshader代码时,用Vulkan的SPIR-V二进制编译shader。

    大意是:使用 SpirV 和反射的唯一解决方案是使用 SpirV-Cross 为 OpenGL 生成的 sharder 源,而不是 spirV 二进制文件。

    For now the only solution i found to keep using SpirV and the reflection is by using sharder sources generated by SpirV-Cross for OpenGL instead fof the spirV binary.

    GLuint program = glCreateProgram();std::vector<GLuint> glShadersIDs;int glShaderIDIndex = 0;
    for (auto&& [stage, spirv] : m_VulkanSPIRV) {spirv_cross::CompilerGLSL glslCompiler(spirv);auto& source = glslCompiler.compile();GLuint shader;shader = glCreateShader(stage);const GLchar* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);HZ_CORE_ERROR("{0}", infoLog.data());HZ_CORE_ASSERT(false, "[OpenGL] Shader compilation failure!");break;}glAttachShader(program, shader);glShadersIDs[glShaderIDIndex++] = shader;
    }
    

    我还没完整运行起来。

    第二种方法比较靠谱:检测是amd显卡就换

我解决此BUG的路线

  • 以为是代码写错

    下载了TheCherno的项目并且修复好运行起来

    • 克隆Hazel项目到本地

      git clone https://github.com/TheCherno/Hazel
      

      2023年2月8日补:

      上面的git命令因为缺少–recursive所以没成功拷贝子模块,用下面的命令,可以省去下面修 复子模块 的步骤

      git clone --recursive https://github.com/TheCherno/Hazel
      
    • 回退到第100集版本

      到Hazel项目的提交历史复制SHA

      运行以下命令,即可回退

      git checkout sha
      
    • 修复子模块

      将.gitmodules文件替换为正确的url地址

      [submodule "Hazel/vendor/spdlog"]path = Hazel/vendor/spdlogurl = https://github.com/gabime/spdlog
      [submodule "Hazel/vendor/GLFW"]path = Hazel/vendor/GLFWurl = https://github.com/TheCherno/glfw
      [submodule "Hazel/vendor/imgui"]path = Hazel/vendor/imguiurl = https://github.com/TheCherno/imgui
      [submodule "Hazel/vendor/glm"]path = Hazel/vendor/glmurl = https://github.com/g-truc/glm
      [submodule "Hazel/vendor/yaml-cpp"]path = Hazel/vendor/yaml-cppurl = https://github.com/TheCherno/yaml-cpp
      [submodule "Hazel/vendor/ImGuizmo"]path = Hazel/vendor/ImGuizmourl = https://github.com/TheCherno/ImGuizmo
      

      再运行下载子模块git命令

      git submodule init
      git submodule update
      
    • 然后拷贝vulkan的lib,重新指向premake脚本,即可

    运行发现还是报同样的错,遂放弃。

  • 以为是vulkan版本错误

    卸载最新的1.3.236.0版本

    • 安装1.2.170.0版本

      但是发现这个版本竟没有Cherno要求的shaderc_sharedd.lib,即后带d的lib文件,于是混用1.3.236.0的shaderc_sharedd.lib文件,运行不出意外还是报错,直接运行不起来,即卸载

      难怪Cherno的python脚本写了验证是否有shaderc_sharedd.lib文件

      @classmethod
      def CheckVulkanSDKDebugLibs(cls):vulkanSDK = os.environ.get("VULKAN_SDK")shadercdLib = Path(f"{vulkanSDK}/Lib/shaderc_sharedd.lib")return shadercdLib.exists()
      
    • 安装1.3.216.0版本

      问题和上一样没有shaderc_sharedd.lib,混用1.3.236.0的shaderc_sharedd.lib文件,报错。即卸载

      error LNK2001: 无法解析的外部符号 "protected: virtual void __cdecl spirv_cross::CompilerGLSL::declare_undefined_values(void)" (?declare_undefined_values@CompilerGLSL@spirv_cross@@MEAAXXZ)
      
  • 收获

    1. 解决方法来自Hazel的GitHub的ISSUE,而我却忘记遇到bug应该在这里找类似的问题。
    2. 搜索谷歌问题有技巧,应该用atio6axx.dll关键字搜索,我用一整段错误信息搜索找不到合适解决方法。

    即:最好先好好查谷歌、GitHub的issue再折腾其它方法。

运行脚本安装Vulkan遇到的问题

  • SyntaxError: (unicode error) ‘utf-8’ codec can’t decode byte 0xa3

    python程序中使用了UTF-8编码,而没有在脚本上申明要使用UTF-8编码。说的更加直接一点,就是用了中文字符,一般我们习惯使用中文作为注释。

    在python脚本程序中,主动申明我们使用UTF-8编码方式。

    申明的方法如下,在程序最上面增加以下语句,尤其是第二句。

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
  • ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:1129)

    搜索一番发现是因为电脑开了代理(科学上网工具)的原因,但是实际上代理是可以正常使用的。

    方法:关闭科学上网

  • 安装了Vulkan但是python脚本却检测不到

    确定在环境变量设置了VULKAN_SDK名称的环境变量

    VULKAN_SDK
    D:\3DGameSDK\Vulkan\1.3.236.0
    

    如果设置了,还是检测不到,就重启IDE

这篇关于Hazel游戏引擎(100)Vulkan、SPIR-V和新Shader系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/200722

相关文章

使用Python实现Windows系统垃圾清理

《使用Python实现Windows系统垃圾清理》Windows自带的磁盘清理工具功能有限,无法深度清理各类垃圾文件,所以本文为大家介绍了如何使用Python+PyQt5开发一个Windows系统垃圾... 目录一、开发背景与工具概述1.1 为什么需要专业清理工具1.2 工具设计理念二、工具核心功能解析2.

Linux系统之stress-ng测压工具的使用

《Linux系统之stress-ng测压工具的使用》:本文主要介绍Linux系统之stress-ng测压工具的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、理论1.stress工具简介与安装2.语法及参数3.具体安装二、实验1.运行8 cpu, 4 fo

MySQL 存储引擎 MyISAM详解(最新推荐)

《MySQL存储引擎MyISAM详解(最新推荐)》使用MyISAM存储引擎的表占用空间很小,但是由于使用表级锁定,所以限制了读/写操作的性能,通常用于中小型的Web应用和数据仓库配置中的只读或主要... 目录mysql 5.5 之前默认的存储引擎️‍一、MyISAM 存储引擎的特性️‍二、MyISAM 的主

ubuntu20.0.4系统中安装Anaconda的超详细图文教程

《ubuntu20.0.4系统中安装Anaconda的超详细图文教程》:本文主要介绍了在Ubuntu系统中如何下载和安装Anaconda,提供了两种方法,详细内容请阅读本文,希望能对你有所帮助... 本文介绍了在Ubuntu系统中如何下载和安装Anaconda。提供了两种方法,包括通过网页手动下载和使用wg

ubuntu系统使用官方操作命令升级Dify指南

《ubuntu系统使用官方操作命令升级Dify指南》Dify支持自动化执行、日志记录和结果管理,适用于数据处理、模型训练和部署等场景,今天我们就来看看ubuntu系统中使用官方操作命令升级Dify的方... Dify 是一个基于 docker 的工作流管理工具,旨在简化机器学习和数据科学领域的多步骤工作流。

使用Python和SQLAlchemy实现高效的邮件发送系统

《使用Python和SQLAlchemy实现高效的邮件发送系统》在现代Web应用中,邮件通知是不可或缺的功能之一,无论是订单确认、文件处理结果通知,还是系统告警,邮件都是最常用的通信方式之一,本文将详... 目录引言1. 需求分析2. 数据库设计2.1 User 表(存储用户信息)2.2 CustomerO

Linux系统调试之ltrace工具使用与调试过程

《Linux系统调试之ltrace工具使用与调试过程》:本文主要介绍Linux系统调试之ltrace工具使用与调试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、ltrace 定义与作用二、ltrace 工作原理1. 劫持进程的 PLT/GOT 表2. 重定

Springboot实现推荐系统的协同过滤算法

《Springboot实现推荐系统的协同过滤算法》协同过滤算法是一种在推荐系统中广泛使用的算法,用于预测用户对物品(如商品、电影、音乐等)的偏好,从而实现个性化推荐,下面给大家介绍Springboot... 目录前言基本原理 算法分类 计算方法应用场景 代码实现 前言协同过滤算法(Collaborativ

Python开发文字版随机事件游戏的项目实例

《Python开发文字版随机事件游戏的项目实例》随机事件游戏是一种通过生成不可预测的事件来增强游戏体验的类型,在这篇博文中,我们将使用Python开发一款文字版随机事件游戏,通过这个项目,读者不仅能够... 目录项目概述2.1 游戏概念2.2 游戏特色2.3 目标玩家群体技术选择与环境准备3.1 开发环境3

Windows系统宽带限制如何解除?

《Windows系统宽带限制如何解除?》有不少用户反映电脑网速慢得情况,可能是宽带速度被限制的原因,只需解除限制即可,具体该如何操作呢?本文就跟大家一起来看看Windows系统解除网络限制的操作方法吧... 有不少用户反映电脑网速慢得情况,可能是宽带速度被限制的原因,只需解除限制即可,具体该如何操作呢?本文