如" 金属着色语言指南"中所述:
片段函数不允许写入缓冲区或纹理.
我明白这是事实,但我很好奇为什么.能够从片段着色器中写入缓冲区是非常有用的 ; 我知道在硬件端可能更复杂的是不提前知道特定线程的内存写入的结束位置,这对于原始缓冲区写入并不总是知道,但这是在Metal计算中公开的一种能力着色器,为什么不在片段着色器中呢?
我应该澄清为什么我认为来自片段函数的缓冲区写入是有用的.在光栅化管道的最常见用例中,三角形被栅格化和着色(根据片段着色器)并写入预定义的存储器位置,在每个片段着色器调用之前已知,并由来自规范化设备坐标和帧的预定义映射确定缓冲.这适用于大多数使用情况,因为大多数情况下您只想将三角形直接渲染到缓冲区或屏幕.
在其他情况下,您可能希望在片段着色器中进行延迟写入,其末端位置基于片段属性而不是片段的确切位置; 有效地,光栅化与副作用.例如,大多数基于GPU的体素化通过从一些期望的角度渲染具有正交投影的场景,然后写入3D纹理,将片段的XY坐标及其相关联的深度值映射到3D纹理中的位置来工作.这在这里描述.
其他用途包括某些形式的与订单无关的透明度(透明度,其中绘制顺序不重要,允许重叠透明对象).一种解决方案是使用多层帧缓冲区,然后在单独的传递中基于它们的深度值对片段进行排序和混合.由于没有硬件支持这样做(在大多数GPU上,我相信英特尔有硬件加速),你必须维护每个像素的原子计数器和手动纹理/缓冲区写入,以协调写入分层帧缓冲区.
另一个例子可能是通过光栅化提取GI的虚拟点光源(即在光栅化时为相关片段写出点光源).在所有这些用例中,都需要来自片段着色器的缓冲区写入,因为ROP仅为每个像素存储一个结果片段.在没有这个特征的情况下获得相同结果的唯一方法是通过某种深度剥离方式,这对于高深度复杂度的场景来说非常慢.
现在我意识到我给出的例子并不是特别关于缓冲区写入,而是更广泛地关于从片段着色器进行动态内存写入的想法,理想情况下是对原子性的支持.缓冲区写入似乎只是一个简单的问题,它们的包含将大大改善这种情况.
由于我在这里没有得到任何答案,我最终在Apple的开发者论坛上发布了这个问题.我在那里得到了更多的反馈,但仍然没有真正的答案.除非我遗漏了某些内容,否则几乎所有官方支持Metal的OS X设备都支持此功能.据我所知,这个功能最初在2009年左右开始出现在GPU中.这是当前DirectX和OpenGL(甚至不考虑DX12或Vulkan)的常见功能,因此Metal将是唯一缺乏它的"尖端"API .
我意识到PowerVR硬件可能不支持此功能,但Apple没有通过功能集区分金属着色语言的问题.例如,iOS上的Metal允许片段着色器内的"自由"帧缓冲器提取,这由高速缓存的PowerVR架构直接支持在硬件中.此功能直接在Metal Shading Language中显示,因为它允许您使用[[color(m)]]
iOS着色器的属性限定符声明片段函数输入.可以说,允许使用device
存储空间限定符声明缓冲区,或者使用纹理access::write
作为片段着色器的输入,对语言的语义更改不会比Apple为优化iOS所做的更多.因此,就我而言,PowerVR缺乏支持并不能解释我在OS X上缺少的功能.
对于大多数C/C++编译器,有一个可传递给编译器的标志-march=native
,它告诉编译器调整生成的代码以用于主机CPU的微架构和ISA扩展.即使它没有使用相同的名称,通常也有基于LLVM的编译器的等效选项,例如rustc
或swiftc
.
根据我自己的经验,这个标志可以为数字密集型代码提供大量的加速,而且听起来它可以免于为你自己的机器编译的代码的妥协.也就是说,我认为我没有看到任何默认启用它的构建系统或静态编译器:
显然,任何要求您传递它的命令行编译器可执行文件都不会默认使用它.
我想不出任何默认启用它的IDE.
我想不出任何共同构建系统我已经与(工作的cmake
,automake
,cargo
,spm
,等),使其能够在默认情况下,即使是最优化的基础之上.
我可以想到一些原因,但没有一个真的令人满意:
使用-march=native
不适合将分发给其他计算机的二进制文件.也就是说,我发现自己为自己的机器编译源代码比其他机器更频繁,这并不能解释它在调试版本中的缺乏用途,因为它没有分发的意图.
至少在Intel x86 CPU上,我的理解是不经常使用AVX指令会降低性能或功率效率,因为AVX单元在不使用时断电,要求它上电以供使用,以及许多Intel CPU下行运行AVX指令.尽管如此,它只解释了为什么不启用AVX,而不是为什么代码不会针对特定微架构处理常规指令而调整.
由于大多数x86 CPU使用具有寄存器重命名的花哨的无序超标量流水线,因此针对特定微架构的调优代码可能并不是特别重要.不过,如果它可以帮助,为什么不使用它呢?
我是朱莉娅的新手,我试图在语言层面理解它ccall
是什么.在语法级别,它看起来像一个普通函数,但它在参数的显示方式上显然不同:
请注意,参数类型元组必须是文字元组,而不是元组值变量或表达式.
另外,如果我评估一个绑定到Julia REPL中的函数的变量,我会得到类似的东西
julia> max
max (generic function with 15 methods)
Run Code Online (Sandbox Code Playgroud)
但如果我尝试做同样的事情ccall
:
julia> ccall
ERROR: syntax: invalid "ccall" syntax
Run Code Online (Sandbox Code Playgroud)
显然,这ccall
是一种特殊的语法,但它也不是一个宏(没有@
前缀,无效的宏使用会产生更具体的错误).那么,它是什么?它是用语言编写的东西,还是我可以用一些我不熟悉的语言构造来定义自己的东西?
如果它是一些烘焙的语法,为什么决定使用函数调用符号,而不是将其实现为宏或设计更可读和不同的语法?
给出下面的代码,其中两个a
和b
是Number
表示符号的32位带符号整数的范围内的值S:
var quotient = ((a|0) / (b|0))|0;
Run Code Online (Sandbox Code Playgroud)
并且假设运行时完全符合ECMAScript 6规范,那么值是否quotient
始终是正确的有符号整数除a
以及b
整数?换句话说,这是一种在JavaScript中实现真正有符号整数除法的正确方法,它等同于机器指令吗?
为了更好地了解优化过程中指针别名不变量的表现方式,我将一些代码插入了著名的Compiler Explorer中,在此重复说明:
#include <cstring>
bool a(int *foo, int *bar) {
(void) *foo, (void) *bar;
return foo == bar;
}
bool b(int *foo, float *bar) {
(void) *foo, (void) *bar;
return foo == reinterpret_cast<int *>(bar);
}
bool c(int *foo, int *bar) {
(void) *foo, (void) *bar;
// It's undefined behavior for memcpyed memory ranges to overlap (i.e. alias)
std::memcpy(foo, bar, sizeof(int));
return foo == bar;
}
bool d(int *__restrict foo, int *__restrict bar) {
(void) *foo, (void) *bar;
return …
Run Code Online (Sandbox Code Playgroud) 一些较新的语言正在将ARC应用到他们的编译器中(Swift和Rust,仅举几例).据我所知,这实现了与运行时GC相同的功能(将手动解除分配的负担远离程序员),同时显着提高效率.
我知道ARC可能会成为一个复杂的过程,但由于现代垃圾收集器的复杂性,实现ARC似乎并不复杂.但是,仍有大量语言和框架使用GC进行内存管理,甚至用于系统编程的Go语言也使用GC.
我真的不明白为什么GC会优于ARC.我在这里错过了什么吗?
我没有锈的别名规则特别深刻的理解(从我听说他们没有扎实的定义),但我无法理解是什么使这个代码示例中std::slice
文档还好.我在这里重复一遍:
let x = &mut [1, 2, 4];
let x_ptr = x.as_mut_ptr();
unsafe {
for i in 0..x.len() {
*x_ptr.offset(i as isize) += 2;
}
}
assert_eq!(x, &[3, 4, 6]);
Run Code Online (Sandbox Code Playgroud)
我在这里看到的问题是x
,作为&mut
参考,可以假设编译器是唯一的.x
get 的内容经过修改x_ptr
,然后通过回读x
,我认为没有理由为什么编译器不能假设x
没有被修改,因为它从未通过唯一的现有&mut
引用进行修改.
那么,我在这里错过了什么?
编译器是否需要假设*mut T
可能是别名&mut T
,即使通常允许它假设&mut T
永远不会别名别名&mut T
?
该unsafe
块是否充当某种别名障碍,编译器假定其中的代码可能已修改了范围内的任何内容?
此代码示例是否已损坏?
如果有某种稳定的规则使这个例子没问题,究竟是什么呢?它的范围是多少?我应该担心多少假设破坏unsafe
Rust代码中的随机事物?
我有兴趣远离Xcode并在项目中为混合语言应用程序手动编译Metal着色器.
不过,我不知道该怎么做.Xcode隐藏了着色器编译的细节以及随后在运行时加载到应用程序中(您只需调用device.newDefaultLibrary()
).这甚至是可能的,还是我必须为我的目的使用运行时着色器编译?
我想编写自己的“小向量”类型,第一个障碍是弄清楚如何实现堆栈存储。
我偶然发现了std::aligned_storage
,它似乎是专门为实现任意堆栈存储而设计的,但我很不清楚什么是安全的,什么是不安全的。cppreference.com方便地提供了一个使用 的示例std::aligned_storage
,我将在这里重复一下:
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an …
Run Code Online (Sandbox Code Playgroud) 我想知道以下哪个代码段最快,假设目标是从指向的T
数量类型的元素中读取并用它们做一些事情。我对循环结构本身的效率特别感兴趣,而不是对元素做了什么。numElements
somePointer
第一候选人
for (int i = 0; i < numElements; i++) {
T val = somePointer[i];
... // Do something
}
Run Code Online (Sandbox Code Playgroud)
第二候选人
T* tempPointer = somePointer;
T* endPointer = somePointer + numElements;
while (tempPointer < endPointer) {
T val = *tempPointer;
... // Do something
tempPointer++;
}
Run Code Online (Sandbox Code Playgroud)
当然,第一个候选更清晰,更不容易出错。但是,如果它实际上被编译成它似乎会生成的代码,我认为它会更慢。使用for
循环需要i
每次循环迭代的增量,以及在取消引用之前somePointer
由 amount指向的地址的偏移量i * sizeOf(t)
。指针递增方法似乎每个循环周期只需要一个加法操作,因此让我相信它会更快。
但是,据我所知,编译器尝试for
使用 SIMD 指令对循环进行矢量化;如果编译器可以成功地检测到在for
循环中进行矢量化的机会但不能使用递增指针,for
那么 then 似乎是更快的选择。当然,就我所知,编译器正在检测for
循环可以转换为指针增量的情况,并在向量化之前进行转换,这将使它变得无关紧要。
简而言之,在实际场景中,哪个更快?
c++ ×3
metal ×2
pointers ×2
borrowing ×1
buffer ×1
c ×1
c++17 ×1
clang ×1
compilation ×1
ecmascript-6 ×1
ffi ×1
function ×1
gcc ×1
integer ×1
javascript ×1
julia ×1
loops ×1
macros ×1
memory ×1
memory-leaks ×1
performance ×1
rust ×1
shader ×1
stdlaunder ×1
syntax ×1
unsafe ×1
xcode ×1