- 博客(79)
- 收藏
- 关注
原创 vscode + git
origin分支:当我们在使用git clone的时候,git会自动地将这个远程的repo命名为origin,拉取它所有的数据之后,创建一个指向它master的指针,命名为origin/master,之后会在本地创建一个指向同样位置的指针,命名为master,和远程的master作为区分。也就是说,origin的含义指的是远程的仓库。它只是一个标记,就和默认分支叫做master一样,本身并没有特别的含义。
2024-03-02 15:05:22
1364
原创 opencv学习二值分析
第一种T(x,y)结果是在(x,y)的邻域blockSize×blockSize范围内所有灰度值的均值减去C�;第二种首先,生成一个大小为blockSize×blockSize的高斯核,作为权重;其次,利用高斯核与(x,y)邻域范围内灰度值,进行加权求和,再减去C,得到T(x,y);高斯核:符合高斯分布,距离越近权重越大。
2024-01-28 10:45:24
968
原创 CUDA 学习记录2
不启用一级缓存(没有缓存的加载不经过一级缓存):在内存段的粒度上(32字节)而不是缓存池的粒度(128字节)执行。更细粒度的加载,可以为非对其或非合并的内存访问带来更好的总线利用率(可能不会减少整体加载时间)。最初的想法肯定是:按照图一合并读更有效率,因为写的时候不需要经过一级缓存,所以对于有一级缓存的程序,合并的读取应该是更有效率的。如果你这么想,恭喜你,你想的不对(我当时也是这么想的)。都是分配的主机内存。注意,零拷贝内存相当于从全局内存中分出的一块独立内存,使用了固定内存技术实现零内存拷贝。
2023-12-19 11:52:01
975
原创 超越openmp通用核心的硬件
程序开始时,有一个初始线程运行在核心0的第一个硬件线程上,我们遇到第一个并行区域,并使用OMP_NUM_THREADS和OMP_PROC_BIND的第一个值(2,spread)。当每个线程遇到嵌套的并行区域时,他们会在同一核心上创建4个线程。控制处理器绑定:OMP_PROC_BIND true,false,master,close(组中的线程被放置在靠近主线程的place,线程以循环的方式从主线程的右边位置开始被分配到连续的place),spread(将线程尽可能均匀的分布在各个place上)
2023-10-14 15:58:43
262
原创 openmp 超越通用核心
配对函数:omp_set_schedule(kind,chunk_size)\omp_get_schedule(kind,chunk_size)omp_set_dynamic //并行区域到下一个并行区域时,优化线程组大小,叫做动态模式。任务默认是绑定的(线程绑定任务),可以用untied子句显示的将任务标记为非绑定状态。环境变量OMP_MAX_TASK_PRIORITY.设置最大优先级。omp_get_max_threads() //获取最大线程数。omp_in_parallel //此时否在并行区域。
2023-10-13 16:22:18
202
原创 openmp 通用核心 学习 2 数据环境—任务-内存模型
具体参考:https://www.openmp.org/spec-html/5.0/openmpsu21.html#:~:text=When%20the%20size%20of%20the%20array%20dimension%20is,the%20lower-bound%20is%20absent%20it%20defaults%20to%200.顺序约束在通用核心中是通过栅栏来定义的。//single构造创建一个共享工作构造,与所有的共享工作构造一样,末尾有一个栅栏,可以使用nowait去除栅栏。
2023-10-12 11:32:59
153
原创 openmp 通用核心 学习 1
在归约操作中,每个迭代步骤都会更新聚合结果,直到迭代完成并得到最终的聚合值。),即一次只有一个线程执行代码,另外的线程有可能在构造的开始处等待,直到轮到自己。归约是一种常见的循环模式,它涉及将一个迭代中的一系列值进行组合(通常是求和、求积等),以生成一个聚合结果。//获得当前时间,start - end获取SPMD算法的运行时间(秒)//获取线程数量(只能在并行区域内使用,在并行区域外使用只能得到1)任何给定循环的迭代中计算的值,都依赖前面迭代产生的值。//跨组内线程的值归约。//共享工作循环构造。
2023-10-11 11:58:46
873
原创 手写线程池,能进行任务窃取的线程池---注释
参考:《C++并发编程实战》对于thread_pool_steal.hpp的代码有改动,不然运行不了,详细代码和测试代码参考下一篇文章。
2023-10-08 21:48:08
153
原创 学习完C++ 并发编程后 手写线程池 最简单的线程池
参考:《C++并发编程实战 第二版》Anthony Willams著 吴天明 译在学习完这本书后想要提高一下编码能力。可以先参考精简版的注释,尝试写一写代码,感觉有难度的话就参考详细版。具体代码和个人遇到的一些问题可以参考我的下一篇博客。
2023-10-07 13:07:24
246
原创 Effective Modern C++ 第七章 并发API 3
这种内存实际上是与外围设备(比如外部传感器或者显示器,打印机,网络端口)通信,而不是读写通常的内存(比如RAM)。有时,一个任务通知另一个异步执行的任务发生了特定的事件很有用,因为第二个任务要等到这个事件发生之后才能继续执行。但是在编译器拿到看起来合理的代码,执行了模板实例化,内联和一系列重排序优化之后,结果会出现冗余访问和无用存储,所以编译器需要摆脱这样的情况并不少见。代表了从被调用方到(通常是异步的)调用方的通信信道的接收端,这里的检测任务和反应任务没有调用-被调用的关系。)都是需要类型参数的模板。
2023-10-06 15:50:53
87
原创 Effective Modern C++ 第七章 并发API 1
遇到负载不均衡问题时,对机器内发生的事情,运行时调度程序比你有更全面的了解,因为它管理的是所有执行过程,而不仅仅个别开发者运行的代码。对比基于线程的编程方式,基于任务的设计为开发者避免了手动线程管理的痛苦,并且自然提供了一种获取异步执行程序的结果(即返回值或者异常)的方式。如果考虑自己实现“在等待结果的线程上运行输出结果的函数”,之前提到了可能引出负载不均衡的问题,这问题不那么容易解决,因为应该是。基于任务的方法通常比基于线程的方法更优,原因之一上面的代码已经表明,基于任务的方法代码量更少。
2023-10-06 15:16:06
90
原创 C++ 并发编程实战 第九章
这样,每个线程就会将新任务放在自己的任务队列上,并且当线程上的任务队列没有任务时,去全局的任务列表中取任务。线程池提供了这样的功能,将提交到线程池中的任务并发执行,提交的任务将会挂在任务队列上。例如:举一个快速排序的例子,一开始的数据块能在线程池上被处理,因为剩余部分会放在工作线程的本地队列上进行处理,这样的使用方式也违背使用线程池的初衷。也了解了如何使用本地任务队列,使用任务窃取的方式减小同步开销,提高线程池的吞吐量,等待子任务完成的同时执行队列中其他任务,从而来避免死锁。同样,在简单例子中使用。
2023-10-02 10:38:52
187
原创 C++ 并发编程实战 第八章 设计并发代码 二
给定的两个矩阵是以行连续的方式存储,这就意味着当访问第一个矩阵的第一行的前N个元素,而后是第二行的前N个元素,以此类推(N是列的数量)。当然,如果N个元素已占有相应的空间,且N个元素也就是每个缓存行上具体的存储元素数量,就会让伪共享的情况消失,因为线程将会对独立缓存行上的数据进行操作。另一方面,当每个线程处理一组行数据,就需要读取第二个矩阵上的数据,还要读取第一个矩阵中的相关行上的值,不过只需要对行上的值进行写入即可。本章关于线程池的部分移除了,线程池——一个预先设定的线程组,会将任务指定给池中的线程。
2023-10-01 15:25:42
206
原创 C++ 并发编程实战 第八章 设计并发代码 一
就能对应用的性能有所改善。因此,当数据已传播,将会有更多的缓存行从处理器的缓存上加载数据,这会增加访问内存的延迟,以及降低数据的性能(与紧凑的数据存储地址相比较)。当处理器切换线程时,要对不同内存行上的数据进行加载(当不同线程使用的数据跨越了多个缓存行时),而非对缓存中的数据保持原样(当线程中的数据都在同一缓存行时)。这个问题的解决办法就是对数据进行构造,让同一线程访问的数据项存在临近的地址中(就像是放在同一缓存行中),这样那些能被独立线程访问的数据将分布在相距很远的地方,并且可能是存储在不同的缓存行中。
2023-10-01 15:10:31
294
原创 C++ 并发编程实战 第七章 设计无锁数据结构
使用互斥量、条件变量,以及future可以用来同步算法和数据结构。调用库函数将会挂起执行线程,直到其他线程完成某个特定动作。库函数将调用阻塞操作来对线程进行阻塞,在阻塞解除前线程无法继续自己的任务。通常,操作系统会完全挂起一个阻塞线程(并将其时间片交给其他线程),直到解阻塞。“解阻塞”的方式很多,比如互斥锁解锁、通知条件变量达成,或让“future状态”就绪。
2023-09-29 21:02:05
601
原创 C++ 并发编程实战 第六章 设计基于锁的并发数据结构
多线程环境下,无数据丢失和损毁,所有的数据需要维持原样,且无条件竞争的数据结构,称之为“线程安全”的数据结构。当线程执行不同的操作时,对同一数据结构的并发操作是安全的,而多线程执行同样的操作时,可能会出现问题。这种情况不可以,所以调用函数需要改成data_cond.notify_all(),这个函数将唤醒所有的工作线程,不过当大多线程发现队列依旧是空时,又会耗费资源让线程重新进入睡眠。虽然,有些数据结构比其他结构的并发访问范围更大,但思路都是一样的:减少保护区域,减少序列化操作,提升并发访问的能力。
2023-09-28 21:32:23
390
原创 C++ 并发编程实战 第五章 C++内存模型和原子操作
存储操作标记为memory_order_release,memory_order_acq_rel或memory_order_seq_cst,加载标记为memory_order_consum,memory_order_acquire或memory_order_sqy_cst,并且操作链上的每一加载操作都会读取之前操作写入的值,因此链上的操作构成了一个释放序列(如果读取对象的加载操作是原子的,那么这个对象的所有修改操作也是原子的,所以加载操作得到的值要么是对象的初始值,要么是某次修改操作存入的值。
2023-09-28 14:54:17
519
原创 C++ 并发编程实战 第四章 并发操作的同步
的两个成员函数wait_for()和wait_until()成员函数分别有两个重载,这两个重载都与wait()成员函数的重载相关——其中一个只是等待信号触发,或超期,亦或伪唤醒,并且醒来时会使用谓词检查锁,并且只有在校验为true时才会返回(这时条件变量的条件达成),或直接超时。的一部分时,当调用抛出一个异常时,这个异常就会存储到future中,之后future的状态置为“就绪”,之后调用get()会抛出已存储的异常(注意:标准级别没有指定重新抛出的这个异常是原始的异常对象,还是一个拷贝。
2023-09-27 15:00:56
733
原创 C++并发编程实战 第三章 在线程间共享数据
private:public:// 1 在构造函数体中的执行拷贝// 在调用pop前,检查栈是否为空// 在修改堆栈前,分配出返回值data.pop();return res;data.pop();
2023-09-26 15:21:53
335
原创 C++并发编程实战 第二版 第二章
这个例子中,已经决定不等待线程结束(使用了detach() ② ),所以当oops()函数执行完成时③,新线程中的函数可能还在运行。内部代码会将拷贝的参数以右值的方式进行传递,这是为了那些只支持移动的类型,而后会尝试以右值为实参调用update_data_for_widget。这段代码中,新线程将会调用my_x.do_lengthy_work(),其中my_x的地址①作为对象指针提供给函数。(move),当原对象是临时变量时,则自动进行移动操作,但当原对象是一个命名变量,转移的时候就需要使用。
2023-09-25 17:24:57
598
原创 CPP-Templates-2nd--第 25 章 元组(Tuples)
C++ (以及 C)也有“异质”的组件:class 或者 struct。本章将会讨论 tuples,它采用了类似于 class 和 struct 的方式来组织数据。比如,一个包含 int,double 和 std::string 的 tuple,和一 个包含 int,double 以及 std::string 类型的成员的 struct 类似,只不过 tuple 中的元素是用位 置信息(比如 0,1,2)索引的,而不是通过名字。
2023-09-18 21:13:52
106
原创 CPP-Templates-2nd--第 24 章 类型列表(Typelists)
高效的编程通常需要用到各种各样的数据结构,元编程也不例外。对于类型元编程,核心的 数据结构是 typelist,和其名字的意思一样,它指的是一个包含了类型的列表。模板元编程 可以操作 typelist 并最终生成可执行程序的一部分。
2023-09-18 19:34:05
417
原创 CPP-Templates-2nd--第 23 章 元编程
元编程的意思是“编写一个程序”。也就是说,我们构建了可以被编程系统用来产生新代码 的代码,而且新产生的代码实现了我们真正想要的功能。通常名词“元编程”暗示了一种自 反的属性:元编程组件是其将要为之产生一部分代码的程序的一部分(比如,程序中一些附 加的或者不同的部分)。为什么需要元编程?和其它编程技术一样,目的是用尽可能少的“付出”,换取尽可能多的 功能,其中“付出”可以用代码长度、维护成本之类的事情来衡量。元编程的特性之一是在 编译期间(at translation time,翻译是否准确?
2023-09-18 16:33:16
164
原创 CPP-Templates-2nd--第 22 章 桥接 static 和 dynamic 多态
在第 18 章中介绍了 C++中 static 多态(通过模板实现)和 dynamic 多态(通过继承和 virtual 函数实现)的本质。两种多态都给程序编写提供了功能强大的抽象,但是也都各有其不足: static 多态提供了和非多态代码一样的性能,但是其在运行期间所使用的类型在编译期就已 经决定。而通过继承实现的 dynamic 多态,则允许单一版本的多态函数适用于在编译期未知 的类型,但是该方式有点不太灵活,因为相关类型必须从统一的基类做继承。
2023-09-18 12:59:40
114
原创 CPP-Templates-2nd--第 21 章 模板和继承
结合以上 IteratorFacade 的定义,可以容易地定义一个指向简单链表的迭代器。
2023-09-18 10:57:53
77
原创 CPP-Templates-2nd--第二十章 基 于 类 型 属 性 的 重 载(Overloading on Type Properties)
函数重载使得相同的函数名能够被多个函数使用,只要能够通过这些函数的参数类型区分它 们就行。对于函数模板,可以在类型模式上进行重载,比如针对指向 T 的指针或者 Array<T>在类型萃取(参考第 19 章)的概念流行起来之后,很自然地会想到基于模板参数对函数模 板进行重载。但是,目前 C++还没有提供任何可以直接基于类型属性进行重载的方法。事实上,上面的两 个模板声明的是完全相同的函数模板,而不是进行了重载,因为在比较两个函数模板的时候 不会比较模板参数的名字。
2023-09-17 20:31:45
243
原创 CPP-Templates-2nd--第十九章 萃取的实现 19.7---
我们可以通过编写基于 SFINAE 的萃取来实现这一功能,这里首先需要测试是否可以像整形类型(比如 int)进行显 式转换,然后依次排除基础类型,class 类型,引用类型,指针类型,还有指向成员的指针 类型(这些类型都可以被转换成整形类型,但是都不是枚举类型)。简单的复合类型包含指针类型,左值以及右值引用 类型,指向成员的指针类型(pointer-to-member types),和数组类型。 最后,为标准库容器类型进行的内存分配是由策略萃取类处理的(参见 std::shared_ptr 的实现)。
2023-09-17 16:02:06
352
原创 CPP-Templates-2nd--第十九章 萃取的实现 19.4-19.5
需要注意的是,如果类型成员 size_type 是 private 的,HasSizeTypeT 会返回 false,因为我们 的萃取模板并没有访问该类型的特殊权限,因此 typename T::size_type 是无效的(触发 SFINAE)。为了使条件有效,必须满足如下条 件: Member 必须能够被用来没有歧义的识别出 T 的一个成员(比如,它不能是重载成员你 函数的名字,也不能是多重继承中名字相同的成员的名字)。相同的技术还可以被用于其它的表达式,甚至是多个表达式的组合。
2023-09-17 12:44:16
232
原创 CPP-Templates-2nd--第十九章 萃取的实现 19.4-19.5
因此,它只会被替换,如果替换失败,基于 SFINAE,相应的 test()声明会 被丢弃掉,因此也就只有应急方案可以匹配相应的调用。为了实现这一目的,我们将 T 传递给 U,并给第一个 test()声明增加一个无名的(dummy)模板参数,该模板参数被一个只有在这一转换有效的 情况下才有效的构造函数进行初始化。1. 向数组类型的转换要始终返回 false,但是在上面的代码中,aux()声明中的类型为 TO 的 参数会退化成指针类型,因此对于某些 FROM 类型,它会返回 true。
2023-09-16 21:42:29
229
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人