当前位置: 首页 > news >正文

并行编程实战——TBB框架的应用之一Supra的基础

一、TBB的应用

在前面分析了TBB框架的各种基本知识和相关的基础应用。这些基础的应用很容易通过学习文档或相关的代码来较为轻松的掌握。为了能够更好的理解TBB框架的优势,这里从一个开源的应用程序来分析一下TBB在其中的更高一层的抽象应用,以方便开发者能够更好的理解和深入掌握TBB框架。也从设计角度为开发者提供了一个TBB应用的方向,从而能够进一步将TBB框架从基础的技术应用上升到架构设计。

二、Supra项目的介绍

SUPRA: Open Source Software Defined Ultrasound Processing for Real-Time Applications。它是一个开源的超声实时应用软件,主要是在医学领域的超声数据的图像重建和输出,同时允许在图像数据的处理过程中对其进行完善和修改。它支持CPU和GPU两种模式,支持2D和3D图像的格式。
SUPRA的整体的框架基础是在TBB的框架基础上进行设计的,它在TBB的任务、节点等抽象的层次上又进行了一次抽象的封装,让其更接近于人们的认知形态。

三、整体架构分析

SUPRA的架构分析将整体略过上层应用的部分,因为这块与UI和实际业务强相关,与今天要分析的TBB没有什么关系。主要谈一下其库的内容设计。SUPRA将整体的设计划分成了几层:
1、TBB节点的抽象层
SUPRA在TBB现有节点的基础上,再次抽象。实现了输入、输出和算法等节点。但是,其这种抽象,与TBB本身的输入、输出完全不同,其设计的节点完全是纯逻辑上的意义。看一下它的代码定义:

class AbstractNode {
protected:typedef tbb::flow::function_node<std::shared_ptr<RecordObject>, std::shared_ptr<RecordObject>, tbb::flow::rejecting>NodeTypeDiscarding;typedef tbb::flow::function_node<std::shared_ptr<RecordObject>, std::shared_ptr<RecordObject>, tbb::flow::queueing>NodeTypeQueueing;typedef tbb::flow::function_node<std::shared_ptr<RecordObject>, tbb::flow::continue_msg, tbb::flow::rejecting>NodeTypeOneSidedDiscarding;typedef tbb::flow::function_node<std::shared_ptr<RecordObject>, tbb::flow::continue_msg, tbb::flow::queueing>NodeTypeOneSidedQueueing;public:/// Base constructor for all nodesAbstractNode(const std::string &nodeID, bool queueing) : m_nodeID(nodeID), m_queueing(queueing) {m_configurationDictionary.setValueRangeDictionary(&m_valueRangeDictionary);}virtual ~AbstractNode() {}virtual size_t getNumInputs() = 0;virtual size_t getNumOutputs() = 0;/// Returns a pointer to the input port with the given numbervirtual tbb::flow::graph_node *getInput(size_t index) { return nullptr; }/// Returns a pointer to the output port with the given numbervirtual tbb::flow::graph_node *getOutput(size_t index) { return nullptr; }const ValueRangeDictionary *getValueRangeDictionary() { return &m_valueRangeDictionary; }const ConfigurationDictionary *getConfigurationDictionary() { return &m_configurationDictionary; }const std::string &getNodeID() { return m_nodeID; }template <typename ValueType> bool changeConfig(const std::string &configKey, const ValueType &newValue) {if (m_valueRangeDictionary.hasKey(configKey) && m_valueRangeDictionary.isInRange(configKey, newValue)) {LOG(INFO) << "Parameter: " << m_nodeID << "." << configKey << " = " << newValue;m_configurationDictionary.set(configKey, newValue);configurationEntryChanged(configKey);return true;}return false;}void changeConfig(const ConfigurationDictionary &newConfig) {configurationDictionaryChanged(newConfig);// validate the configuration entriesConfigurationDictionary validConfig = newConfig;validConfig.setValueRangeDictionary(&m_valueRangeDictionary);validConfig.checkEntriesAndLog(m_nodeID);// store all valid entriesm_configurationDictionary = validConfig;configurationChanged();}std::string getTimingInfo() { return m_callFrequency.getTimingInfo(); }protected:/// The collection of node parametersConfigurationDictionary m_configurationDictionary;/// The definition of parameters and their respective rangesValueRangeDictionary m_valueRangeDictionary;CallFrequency m_callFrequency;bool m_queueing;protected:virtual void configurationEntryChanged(const std::string &configKey) {}virtual void configurationChanged() {}virtual void configurationDictionaryChanged(const ConfigurationDictionary &newConfig) {}private:std::string m_nodeID;
};
class AbstractInput : public AbstractNode {
public:/// Base constructor for the input node. Initializes its output ports.AbstractInput(tbb::flow::graph &graph, const std::string &nodeID, size_t numPorts): AbstractNode(nodeID, false), m_numOutputs(numPorts) {m_pOutputNodes.resize(m_numOutputs);for (size_t i = 0; i < m_numOutputs; i++) {m_pOutputNodes[i] = std::unique_ptr<tbb::flow::broadcast_node<std::shared_ptr<RecordObject>>>(new tbb::flow::broadcast_node<std::shared_ptr<RecordObject>>(graph));}}~AbstractInput() { waitForFinish(); }void waitForFinish() {if (m_pInputDeviceThread && m_pInputDeviceThread->joinable()) {m_pInputDeviceThread->join();}}void detachThread() { this->m_pInputDeviceThread->detach(); }void start() {setRunning(true);m_pInputDeviceThread = std::make_shared<std::thread>(std::thread([this]() { this->startAcquisition(); }));}/// Set the state of the input node, if newState is false, the node is stoppedvirtual bool setRunning(bool newState) {bool oldState = m_running;m_running = newState;if (!m_running) {stopAcquisition();}return (oldState || newState) && !(oldState && oldState);}/// Returns whether the node is runningbool getRunning() { return m_running; }/// returns the output port with the given indextemplate <size_t index> tbb::flow::broadcast_node<std::shared_ptr<RecordObject>> &getOutputNode() {return *std::get<index>(m_pOutputNodes);}virtual size_t getNumInputs() { return 0; }/// returns the number of output ports of this nodevirtual size_t getNumOutputs() { return m_pOutputNodes.size(); }/// returns a pointer to the output port with the given indexvirtual tbb::flow::graph_node *getOutput(size_t index) {if (index < m_pOutputNodes.size()) {return m_pOutputNodes[index].get();}return nullptr;}protected:/// The nodes output. An implementing node calls this method when it has a/// dataset to send into the graph.template <size_t index> bool addData(std::shared_ptr<RecordObject> data) {return m_pOutputNodes[index]->try_put(data);}double getTimerFrequency() { return m_timer.getFrequency(); }void setUpTimer(double frequency) { m_timer.setFrequency(frequency); }void timerLoop() {bool shouldContinue = true;while (shouldContinue) {shouldContinue = timerCallback();if (shouldContinue) {m_timer.sleepUntilNextSlot();}}}private:std::vector<std::unique_ptr<tbb::flow::broadcast_node<std::shared_ptr<RecordObject>>>> m_pOutputNodes;SingleThreadTimer m_timer;std::shared_ptr<std::thread> m_pInputDeviceThread;std::atomic_bool m_running;protected:std::mutex m_mutex;const size_t m_numOutputs;// Functions to be overwritten
public:virtual void initializeDevice() {}virtual bool ready() { return false; }virtual std::vector<size_t> getImageOutputPorts() = 0;virtual std::vector<size_t> getTrackingOutputPorts() = 0;virtual void freeze() = 0;virtual void unfreeze() = 0;protected:/// The entry point for the implementing input node/// This method is called in a separate thread once the node is started.virtual void startAcquisition() = 0;virtual void stopAcquisition() {}virtual bool timerCallback() { return false; }
};

其它节点的代码就不拷贝上来了。从上面的代码可以看到,其节点的设计几乎等同于应用的逻辑表达了,也就是说抽象的层次更高了。

2、TBB节点抽象后的动态管理层
动态管理层分为几部分:首先是节点的性质、参数以太连接状态等由一个XML配置文件来实现。当然,它也支持在代码中完全动态的调整;其次,节点的管理注册由一个专门的工厂类的实现,包括节点的创建和连接等;最后,它实现了对节点重点参数的动态修改(实现了一套相关的XML自动映射机制)。
这里暂时只关注一下工厂类的处理:

std::shared_ptr<AbstractOutput> InterfaceFactory::createOutputDevice(shared_ptr<tbb::flow::graph> pG,const std::string &nodeID, std::string deviceType,bool queueing) {std::shared_ptr<AbstractOutput> retVal = std::shared_ptr<AbstractOutput>(nullptr);if (deviceType == "OpenIGTLinkClientOutputDevice") {retVal = std::make_shared<OpenIGTLinkClientOutputDevice>(*pG, nodeID, queueing);}if (deviceType == "DatasCacheOutputDevice") {retVal = std::make_shared<DatasCacheOutputDevice>(*pG, nodeID, queueing);}LOG_IF(ERROR, !((bool)retVal)) << "Error creating output device. Requested type '" << deviceType<< "' is unknown. Did you activate the corresponding "<< "module in the build of the library?";LOG_IF(INFO, (bool)retVal) << "Created output device '" << deviceType << "' with ID '" << nodeID << "'";return retVal;
}

在后面会对这一块进行较详细的分析。
3、数据处理层
在每个功能节点,都会有类似下面的writeData来连接抽象节点与TBB节点之间的联系,来处理数据:

  AbstractOutput(tbb::flow::graph &graph, const std::string &nodeID, bool queueing) : AbstractNode(nodeID, queueing) {if (queueing) {m_inputNode = std::unique_ptr<NodeTypeOneSidedQueueing>(new NodeTypeOneSidedQueueing(graph, 1, [this](const std::shared_ptr<RecordObject> &inMessage) {if (this->m_running) {writeData(inMessage);}}));} else {m_inputNode = std::unique_ptr<NodeTypeOneSidedDiscarding>(new NodeTypeOneSidedDiscarding(graph, 1, [this](const std::shared_ptr<RecordObject> &inMessage) {if (this->m_running) {writeData(inMessage);}}));}}

这样就非常巧妙的把二者整合到一想,非常值得借鉴。

4、算法处理层

其算法处理层就是在节点中拿到数据后,对相关数据进行处理,比如各种图像的处理、数据的压缩等等:

	shared_ptr<RecordObject> BeamformingMVNode::checkTypeAndBeamform(shared_ptr<RecordObject> inObj){unique_lock<mutex> l(m_mutex);shared_ptr<USImage> pImageRF = nullptr;if (inObj->getType() == TypeUSRawData){shared_ptr<const USRawData> pRawData = dynamic_pointer_cast<const USRawData>(inObj);if (pRawData){if (pRawData->getImageProperties()->getImageState() == USImageProperties::RawDelayed){m_callFrequency.measure();switch (pRawData->getDataType()){case TypeInt16:pImageRF = beamformTemplated<int16_t>(pRawData);break;case TypeFloat:pImageRF = beamformTemplated<float>(pRawData);break;default:logging::log_error("BeamformingMVNode: Input rawdata type is not supported.");break;}m_callFrequency.measureEnd();if (m_lastSeenImageProperties != pImageRF->getImageProperties()){updateImageProperties(pImageRF->getImageProperties());}pImageRF->setImageProperties(m_editedImageProperties);}else {logging::log_error("BeamformingMVNode: Cannot beamform undelayed RawData. Apply RawDelayNode first");}}else {logging::log_error("BeamformingMVNode: could not cast object to USRawData type, is it in supported ElementType?");}}return pImageRF;}

这个函数checkTypeAndBeamform(类似于writedata函数)内部调用了:

	template <typename RawDataType>std::shared_ptr<USImage> BeamformingMVNode::beamformTemplated(shared_ptr<const USRawData> rawData){shared_ptr<USImage> pImageRF = nullptr;cudaSafeCall(cudaDeviceSynchronize());cublasSafeCall(cublasSetStream(m_cublasH, rawData->getData<RawDataType>()->getStream()));switch (m_outputType){case supra::TypeInt16:pImageRF = RxBeamformerMV::performRxBeamforming<RawDataType, int16_t>(rawData, m_subArraySize, m_temporalSmoothing, m_cublasH, m_subArrayScalingPower, m_computeMeans);break;case supra::TypeFloat:pImageRF = RxBeamformerMV::performRxBeamforming<RawDataType, float>(rawData, m_subArraySize, m_temporalSmoothing, m_cublasH, m_subArrayScalingPower, m_computeMeans);break;default:logging::log_error("BeamformingMVNode: Output image type not supported:");break;}cudaSafeCall(cudaDeviceSynchronize());return pImageRF;}

这其实就进入了算法的处理。后面复杂的算法处理就不拷贝上来了,有兴趣可以自己看看。
5、输入输出层
这个也非常值得借鉴,数据的输入和经过TBB算法处理后的数据需要传递给相关的各方,此处SUPRA也提供了很好的范例

//输入bool UltrasoundInterfaceRawDataMock::timerCallback() {if (!m_frozen){double timestamp = getCurrentTime();m_callFrequency.measure();shared_ptr<USRawData> pRawData = std::make_shared<USRawData>(m_protoRawData->getNumScanlines(),m_protoRawData->getNumElements(),m_protoRawData->getElementLayout(),m_protoRawData->getNumReceivedChannels(),m_protoRawData->getNumSamples(),m_protoRawData->getSamplingFrequency(),m_pMockData,m_protoRawData->getRxBeamformerParameters(),m_protoRawData->getImageProperties(),getCurrentTime(),getCurrentTime());addData<0>(pRawData);if (!m_singleImage){if (m_lastFrame){setRunning(false);}else{readNextFrame();}}m_callFrequency.measureEnd();}return getRunning();}

输入节点通过读取MOCK的文件数据,来复现实际的图像和相关的数据。再看一下输出:

//输出void OpenIGTLinkOutputDevice::writeData(std::shared_ptr<RecordObject> data){if (m_isReady && getRunning() && m_isConnected){m_callFrequency.measure();sendMessage(data);m_callFrequency.measureEnd();}}//最终调用template <typename T>void OpenIGTLinkOutputDevice::sendImageMessageTemplated(shared_ptr<const USImage> imageData){static_assert(std::is_same<T, uint8_t>::value ||std::is_same<T, int16_t>::value ||std::is_same<T, float>::value,"Image only implemented for uchar, short and float at the moment");auto properties = imageData->getImageProperties();if (properties->getImageType() == USImageProperties::BMode ||properties->getImageType() == USImageProperties::Doppler){double resolution = properties->getImageResolution();vec3s imageSize = imageData->getSize();igtl::ImageMessage::Pointer pImageMsg = igtl::ImageMessage::New();pImageMsg->SetDimensions((int)imageSize.x, (int)imageSize.y, (int)imageSize.z);pImageMsg->SetSpacing(resolution, resolution, resolution);if (is_same<T, uint8_t>::value){pImageMsg->SetScalarTypeToUint8();}if (is_same<T, int16_t>::value){pImageMsg->SetScalarTypeToInt16();}if (is_same<T, float>::value){pImageMsg->SetScalarType(igtl::ImageMessage::TYPE_FLOAT32);}pImageMsg->SetEndian(igtl::ImageMessage::ENDIAN_LITTLE);igtl::Matrix4x4 m;igtl::IdentityMatrix(m);m[0][0] = -1;m[1][1] = -1;pImageMsg->SetMatrix(m);pImageMsg->SetNumComponents(1);pImageMsg->SetDeviceName(m_streamName.c_str());pImageMsg->AllocateScalars();igtl::TimeStamp::Pointer pTimestamp = igtl::TimeStamp::New();double timestampSeconds;double timestampFrac = modf(imageData->getSyncTimestamp(), &timestampSeconds);pTimestamp->SetTime((uint32_t)timestampSeconds, (uint32_t)(timestampFrac*1e9));pImageMsg->SetTimeStamp(pTimestamp);auto imageContainer = imageData->getData<T>();if (!imageContainer->isHost()){imageContainer = make_shared<Container<T> >(LocationHost, *imageContainer);}size_t numElements = imageSize.x * imageSize.y * imageSize.z;memcpy(pImageMsg->GetScalarPointer(), imageContainer->get(), numElements * sizeof(T));pImageMsg->Pack();int sendResult = m_clientConnection->Send(pImageMsg->GetPackPointer(), pImageMsg->GetPackSize());if (sendResult == 0) //when it could not be sent{m_isConnected = false;log_info("IGTL: Lost connection. Waiting for next connection.");waitAsyncForConnection();}}}

这个输出节点提供的是医疗上常用的IGTL通信模块来做为输出节点的最终通信方式。

四、总结

之所以从SUPRA框架入手,最主要的就是其在设计上有机的整合了TBB框架,将业务逻辑更好的与TBB框架的设计再次抽象,在整体流程实现的过程中,实现了业务逻辑与底层技术的动态组合。是一个非常值得借鉴的设计。

相关文章:

并行编程实战——TBB框架的应用之一Supra的基础

一、TBB的应用 在前面分析了TBB框架的各种基本知识和相关的基础应用。这些基础的应用很容易通过学习文档或相关的代码来较为轻松的掌握。为了能够更好的理解TBB框架的优势&#xff0c;这里从一个开源的应用程序来分析一下TBB在其中的更高一层的抽象应用&#xff0c;以方便开发…...

std::vector

std::vector是C标准库中一个非常强大的容器类&#xff0c;它提供了动态数组的功能。std::vector可以自动调整大小&#xff0c;提供了随机访问的能力&#xff0c;同时还支持在序列的尾部高效地添加和删除元素。 当创建一个空的std::vector对象时&#xff0c;它不分配任何内存&a…...

Java Web 之 Cookie 详解

在 JavaWeb 开发中&#xff0c;Cookie 就像网站给浏览器贴的小纸条&#xff0c;用于记录一些用户信息或状态&#xff0c;方便下次访问时识别用户身份或进行个性化服务。 也可以这么理解&#xff1a; 场景一&#xff1a;想象一下&#xff0c;你去一家咖啡店&#xff0c;店员认…...

linux系统下让.py文件开机自启动

一 创建服务文件 1、打开终端 2、切换到root用户 sudo su3、创建一个新的systemd服务文件 nano /etc/systemd/system/total_test0619.service 4、在服务文件中添加以下内容 [Unit] DescriptionRun total_test0619.py at startup[Service] Typesimple ExecStart/usr/bin/n…...

linux远程桌面:xrdp 安装失败

window 如何远程 Linux 桌面 安装xrdp yum install xrdpsystemctl start xrdp 如果找不到软件包&#xff0c;就安装epel源&#xff0c;最好改成国内镜像的 在 /etc/yum.repos.d/ 下创建epel.repo,内容如下 [epel] nameExtra Packages for Enterprise Linux 7 - $basearch …...

9.30Python基础-元组(补充)、字典、集合

Python元组&#xff08;tuple&#xff09;补充 1、元组的不可变性 元组&#xff08;tuple&#xff09;是Python中的一种内置数据类型&#xff0c;用于存储不可变的序列。虽然元组本身不可变&#xff0c;但元组内的元素如果是可变对象&#xff08;如列表&#xff09;&#xff…...

桥接模式和NET模式的区别

桥接模式和NET模式的区别 NAT模式&#xff1a; NAT&#xff1a;网络地址转换&#xff08;模式&#xff09;&#xff1a;借助宿主机来上网&#xff0c;没桥接那么麻烦&#xff0c;只用配置DNS即可。 缺点&#xff1a;扎根于宿主机&#xff0c;不能和局域网内其它真实的主机进行…...

Pigar:Python 项目的依赖管理利器

&#x1f31f; 引言 在Python项目开发过程中&#xff0c;依赖管理是一个不可忽视的环节。一个精确且易于维护的requirements.txt文件对于项目的部署和协作至关重要。今天&#xff0c;我们将介绍一款名为Pigar的自动生成requirements.txt文件的依赖管理工具&#xff0c;它通过一…...

泰勒图 ——基于相关性与标准差的多模型评价指标可视化比较-XGBoost、sklearn

1、基于相关性与标准差的多模型评价指标可视化比较 # 数据读取并分割 import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split plt.rcParams[font.family] = Times New Roman plt.rcParams[axes.unic…...

记录|Modbus-TCP产品使用记录【摩通传动】

目录 前言一、摩通传动实验图1.1 配置软件 IO_Studio1.2 测试软件Modbus Poll1.2.1 读写设置测试1.2.2 AI信号的读取 1.3 对应的C#连接Modbus的测试代码如下【自制&#xff0c;仅供参考】1.4 最终实验图 更新时间 前言 参考文章&#xff1a; 自己需要了解和对比某些产品的Modbu…...

工业交换机的RMON

工业交换机在现代网络中扮演着至关重要的角色&#xff0c;它不仅负责数据的高效传输&#xff0c;还具备强大的监控和管理能力。其中&#xff0c;RMON&#xff08;远程监控&#xff09;功能使得交换机的性能得以进一步提升&#xff0c;成为网络管理的重要工具。RMON提供了一种先…...

生态遥感数据下载分享

中国土壤湿度/土壤水分数据集&#xff08;2000-2020&#xff09; 下载网站&#xff1a;https://poles.tpdc.ac.cn/zh-hans/data/49b22de9-5d85-44f2-a7d5-a1ccd17086d2/#:~:text%E6%88%91%E4%BB%AC%E6%8F%90%E4%BE%9B%E4%BA%86%E4%B8%AD%E5%9B%BD%E8%8C%83 note: The data can …...

ECharts 快速使用

最终效果 使用介绍 echarts图表的绘制&#xff0c;大体分为三步&#xff1a; 根据 DOM实例&#xff0c;通过 echarts.init方法&#xff0c;生成 echarts实例构建 options配置对象&#xff0c;整个echarts的样式&#xff0c;皆有该对象决定最后通过实例.setOption方法&#xf…...

进程--消息队列和共享内存

目录 消息队列 创建消息队列 删除消息队列 发送消息和接收 消息队列 消息队列就是一个消息的列表&#xff0c;进程可以在消息队列中添加消息和的读取消息 消息队列具有FIFO的特性&#xff0c;具有无名管道与有名管道各自的优势&#xff0c;可以支持任意两个进程的进程间通讯…...

useCallback()

官网直达&#xff1a;https://zh-hans.react.dev/reference/react/useCallback 点击按钮&#xff0c;子组件会重新渲染 import { memo, useState, useCallback } from react;const Child (props) > {console.log(我是子组件&#xff01;我在渲染呢&#xff01;&#xff0…...

Python面试题精选及解析--第二篇

在Python的面试中&#xff0c;除了基础语法和常用库的知识外&#xff0c;面试官往往还会通过一系列的问题来考察应聘者的逻辑思维、问题解决能力以及项目经验。以下是一些精心挑选的Python面试题及其详细答案&#xff0c;旨在帮助求职者更好地准备面试。 面试题一&#xff1a;…...

Linux操作常用问题

目录 Ubuntu操作问题vi编辑方向键键盘乱码回退键不能使用的问题解决问题的方法 Ubuntu操作问题 vi编辑方向键键盘乱码回退键不能使用的问题 编辑/etc/systemd/resolved.conf文件来修改DNS&#xff0c;结果编辑时键盘乱码&#xff0c;按下方向键会出现ABCD&#xff0c;且回退键…...

汽车发动机系统(ems)详细解析

汽车发动机系统EMS&#xff0c;即Engine-Management-System&#xff08;发动机管理系统&#xff09;&#xff0c;是现代汽车电子控制技术的重要组成部分。以下是对汽车发动机系统EMS的详细解析&#xff0c;内容将涵盖其定义、工作原理、主要组成、功能特点、技术发展以及市场应…...

对比学习训练是如何进行的

对比学习&#xff08;Contrastive Learning&#xff09;是一种自监督学习的方法&#xff0c;旨在通过拉近相似样本的表示、拉远不相似样本的表示来学习特征表示。在训练过程中&#xff0c;模型并不依赖标签&#xff0c;而是通过样本之间的相似性进行学习。以下是对比学习的基本…...

React 生命周期 - useEffect 介绍

在 React 中&#xff0c;useEffect 钩子可以被看作是函数组件中的一种副作用管理工具&#xff0c;它的行为可以模拟类组件中的不同生命周期方法。useEffect 的执行时机取决于其依赖项数组&#xff08;第二个参数&#xff09;的设置方式。 根据 useEffect 的使用方式&#xff0c…...

OpenCV-指纹识别

文章目录 一、意义二、代码实现1.计算匹配点2.获取编号3.获取姓名4.主函数 三、总结 一、意义 使用OpenCV进行指纹识别是一个复杂且挑战性的任务&#xff0c;因为指纹识别通常需要高精度的特征提取和匹配算法。虽然OpenCV提供了多种图像处理和计算机视觉的工具&#xff0c;但直…...

IPD的核心思想

IPD是一套领先的、成熟的研发管理思想、模式和方法。它是根据大量成功的研发管理实践总结出来的&#xff0c;并被大量实践证明的高效的产品研发模式。 那么&#xff0c;按照IPD来开展产品研发与产品管理工作&#xff0c;应该基于哪些基本思想或原则&#xff1f;市场导向、客户…...

如何在算家云搭建MVSEP-MDX23(音频分离)

一、MVSEP-MDX23简介 模型GitHub网址&#xff1a;MVSEP-MDX23-music-separation-model/README.md 在 main ZFTurbo/MVSEP-MDX23-音乐分离模型 GitHub 上 在音视频领域&#xff0c;把已经发布的混音歌曲或者音频文件逆向分离一直是世界性的课题。音波混合的物理特性导致在没有…...

常用的Java安全框架

Spring Security&#xff1a; 就像Java安全领域的“瑞士军刀”&#xff0c;功能全面且强大。 支持认证、授权、加密、会话管理等安全功能。 与Spring框架无缝集成&#xff0c;使用起来特别方便。 社区活跃&#xff0c;文档丰富&#xff0c;遇到问题容易找到解决方案。 Apach…...

使用 PHP 的 strip_tags函数保护您的应用安全

在现代 web 开发中&#xff0c;处理用户输入是一项常见的任务。然而&#xff0c;用户输入的内容往往包含 HTML 或 PHP 标签&#xff0c;这可能会导致安全漏洞&#xff0c;如跨站脚本攻击&#xff08;XSS&#xff09;。为了解决这个问题&#xff0c;PHP 提供了一个非常有用的函数…...

您的计算机已被Lockbit3.0勒索病毒感染?恢复您的数据的方法在这里!

导言 在数字化时代&#xff0c;互联网已成为我们生活、工作和学习中不可或缺的一部分。然而&#xff0c;随着网络技术的飞速发展&#xff0c;网络安全威胁也日益严峻。其中&#xff0c;勒索病毒作为一种极具破坏性的网络攻击手段&#xff0c;正逐渐成为企业和个人面临的重大挑…...

经典sql题(十二)UDTF之Explode炸裂函数

1. EXPLODE: UDTF 函数 1.1 功能说明 EXPLODE 函数 是Hive 中的一种用户定义的表函数&#xff08;UDTF&#xff09;&#xff0c;用于将数组或映射结构中的复杂的数据结构每个元素拆分为单独的行。这在处理复杂数据时非常有用&#xff0c;尤其是在需要将嵌套数据“打散”以便更…...

【AIGC】ChatGPT提示词解析:如何打造个人IP、CSDN爆款技术文案与高效教案设计

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;打造个人IP爆款文案提示词使用方法 &#x1f4af;CSDN爆款技术文案提示词使用方法 &#x1f4af;高效教案设计提示词使用方法 &#x1f4af;小结 &#x1f4af;前言 在这…...

【Ubuntu】Ubuntu常用命令

文章目录 网卡路由常用命令&#xff1a;编辑文件echo 权限设置gcc编译器&#xff1a; 重启网络服务 sudo service network-manager restart 网卡 #查看网卡信息 ip a #区分光网卡电网卡 sudo lshw -class network -businfo ifconfig ifconfig eth1 192.168.1.12/24 #重启网卡…...

架构设计笔记-5-软件工程基础知识-2

知识要点 构件组装是将库中的构件经适当修改后相互连接,或者将它们与当前开发项目中的软件元素连接,最终构成新的目标软件。 构件组装技术大体可分为: 1. 基于功能的组装技术:基于功能的组装技术采用子程序调用和参数传递的方式将构件组装起来。它要求库中的构件以子程序…...

学做网站必须php吗/软文推广发布

开发环境&#xff1a;系统环境&#xff1a;龙芯1B开发板(mips32指令)、Linux 3.0.0内核编译环境&#xff1a;Ubuntu 10.04 ,gcc-3.4.6-2f本文要用到的相关文件(cramfs-1.1.tar.gz、yaffs2-d43e901.tar.gz、mtd-utils-1.0.0.tar.gz)下载&#xff1a;用户名与密码都是www.linuxid…...

企业网站开发/网络舆情分析报告模板

摘要&#xff1a; 一、背景介绍近年来&#xff0c;越来越热的云计算被推倒风口浪尖&#xff0c;各大中型企业纷纷把企业服务迁移到云上&#xff0c;众多的创业公司也把云服务器作为数据服务的首选。那么问题来了&#xff0c;有些企业的运维开始担心上云的过程是否能做到简单和平…...

彩票做网站犯法吗/长春seo整站优化

本文研究全球与中国市场宠物项圈的发展现状及未来发展趋势&#xff0c;分别从生产和消费的角度分析宠物项圈的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的…...

免费设计房屋的网站/淘宝关键词top排行榜

(1) 十进制转换为二进制&#xff0c;分为整数部分和小数部分① 整数部分方法&#xff1a;除2取余法&#xff0c;即每次将整数部分除以2&#xff0c;余数为该位权上的数&#xff0c;而商继续除以2&#xff0c;余数又为上一个位权上的数&#xff0c;这个步骤一直持续下去&#xf…...

wordpress批量上传文章/北京谷歌seo

【黑客联盟2016年12月02日讯】北京时间11月30日晚间消息&#xff0c;互联网安全公司今日发布报告称&#xff0c;一款名为“Gooligan”的特洛伊木马程序将自己伪装成合法应用(App)入侵Android智能手机和平板电脑&#xff0c;自8月份以来已控制了100多万个谷歌账号。 StopWatch、…...

做网站后台服务器什么最好/佛山网站建设

本文使用的是alibaba的Dubbo。Dubbo整合Springboot可以分为四步&#xff1a;第一步&#xff1a;首先需要了解Dubbo官方给的建议&#xff0c;至少有三个工程&#xff1a;接口工程&#xff1a;主要存实体bean和业务接口服务提供者&#xff1a;业务接口的实现类和调用数据持久层&a…...