V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  Projection  ›  全部回复第 1 页 / 共 4 页
回复总数  77
1  2  3  4  
首先我重新定义一下两个名词:定制和精简。定制是指以标准的方法(能在微软文档找到)对系统进行修改以满足使用需求;精简则是以标准或非标准的方法对系统进行修改以达到减少资源占用的目的。他们部分存在一些重叠,比如关闭某个功能、删除某个应用等。

你可以根据自己的技术能力,选择不同的操作方式,按复杂程度大致可以分成四个层级:

1. 使用原版系统(包括 LTSC 在内),然后去设置里面逐个关掉不需要的功能(有些关不了或不彻底)
2. 安装原版系统,然后使用无人值守文件或 winutil 这类优化工具做一个在线定制(实际上是运行一些脚本,以修改注册表、运行某些程序为主)
3. 离线定制系统,提前把设置都搞好,这样装完系统就是你定制好的样子
4. 离线定制、精简系统(可能暴力删除文件)

但 Windows 是一个闭源系统,没有对 Windows 系统的了解( IT 运维层面)和一定的逆向能力,就很难真正搞清楚各个组件之间的依赖关系,大多数时候只能靠猜。即便猜对了完美删除了某个系统功能,也无法保证第三方软件不会因此出问题(一些软件假定系统具备某个功能)。比如我自己遇到过,删了 Print Spooler 服务之后,Acrobat 安装的时候就直接回滚了。winutil 之前也出现过一个问题:删除 Recall 导致文件资源管理器出现问题,因此将代码回滚了,后来发现是微软犯了一个错误:

https://www.youtube.com/watch?v=G9FRadIkkE0
https://www.youtube.com/watch?v=gBqIUkmVel8

很多所谓的“大神”其实只是投入了更多时间去反复试错,积累了更多的经验。甚至有些人不懂逆向分析、不会 Windows 编程,连自动化脚本都不会写,只是重复性地在 NTLite 这类工具点点按钮、敲敲键盘,最后还说耗费了很多精力(可不吗)。其实只要掌握一点自动化工具的使用,发布新版本的精简系统并不是什么难事。

从技术的角度上来说,精简无非是保证系统不去使用某个文件,然后就可以安全地把这个文件删除或修改了。

我对精简始终保持一个怀疑态度,即便是 Windows 的开发人员也未必完全了解整个系统的组件的依赖关系。Windows 系统很复杂了,纯粹依靠经验和试错是不行的。如果定制系统禁用某个功能,就像 if (false) 一样,那是可以接受的。但如果是暴力删除文件等行为导致这个功能失效,则是非常严重的错误。尽管他们在结果上相同,但后者是不可以接受的,经验法则此时就无法区分出两种情况。

比如 Windows 10 的拼音输入法的候选框依赖于另一个看起来毫不相关的 appx ,删除就不显示候选框和 emoji 面板: https://www.423down.com/13227.html/comment-page-7#comment-340247 。可见这些作者也只是(只能)根据经验来精简系统。

而且 Windows 的变化非常频繁,微软经常塞一堆新功能进来,想精简最新系统就必须不停追踪更新,了解每次更新新增了什么内容,怎么去除这些新组件。这就要求你经常在网络上搜索资料,在各大社区(比如 My Digital Life )追踪最新的修改方法。更别提现在很多功能都捆绑在一起了,根本无法部分移除,修改 DLL 等二进制文件吗?不太现实,数字签名也不会允许这么做。

另一个不容忽视的问题是安全。即使精简版系统的制作者主观上没有恶意,他也很难保证整个构建链条的安全性,尤其是使用了一些非官方的闭源工具的时候,一些系统封装工具可能是重灾区。我想起很旧以前用老毛桃官方 PE 重装系统的经历:那次我安装的是原版镜像,用的是官方最新版的 WinNTSetup ,但浏览器主页还是被篡改了,我发现后又立马重装了。修改主页还是可以被观测到的轻微的问题,谁知道是否还有其他更加恶劣的行为。我现在只使用官方的 PE (只有一个 CMD 窗口)——在系统安全性这件事上,再怎么小心都不为过。

如果是安装在虚拟机中,而且对安全性没有要求,比如测试一个离线运行的软件,那使用这些精简版系统其实也挺方便。有时候我也装来看看,才知道哦原来这个功能是可以关闭的啊,学习了。我自己也会做一些定制,但目的不是为了节省硬盘空间或显著提升性能,只是为了不想看到 Windows 那些乱七八糟的预装内容,眼不见为净。我不提倡暴力精简(以减少镜像体积),但我提倡定制系统来禁用不需要的功能。

我设想过做二进制可重现构建的 WIM 镜像,但发现这需要非常多的工作,尤其是在 Windows 闭源的现状下——哪怕只是挂载一次注册表、什么都不改,它的内容也会发生变化。这样我还得去理解注册表的文件结构以及为什么改变了,太累了!其实我有点羡慕 Linux ,一切都是公开的,或许哪天我会把 Linux 当成主力吧。

https://github.com/ChrisTitusTech/winutil
https://github.com/memstechtips/UnattendedWinstall
35 天前
回复了 tenserG 创建的主题 算法 面试遇到怪题,大家有什么思路吗
先留个保底,然后从 [0, m - n] 区间内随机 n - 1 次划分出 n 个区间,每个人取一个区间 + 保底
@Projection 纠正:文件名是 Ubuntu2204LTS-230518_x64.appx
之前也可以啊,只要有 rootfs.tar.(gz) 就可以创建发行版的多个实例。

比如 Ubuntu-22.04 ,从下面的地址中找到 Ubuntu2004-230608_x64.appx 文件并下载:

https://github.com/microsoft/WSL/blob/master/distributions/DistributionInfo.json

然后解压出 installer.tar.gz 就可以用 wsl --import 命令创建多个实例了。(现在好像多了个 wsl --install --from-file ,你可以试试)

除了这种微软提供的 rootfs ,还有其他渠道也可以获得 rootfs 。不过我一般是通过 Docker 定制一个镜像导入到 WSL 中。
88 天前
回复了 hildred 创建的主题 程序员 各位大佬求推荐 PDF 预览相关的 JS 库
再推荐一个可选项 MuPDF
239 天前
回复了 teli 创建的主题 Python Python 升序、查找高效的集合方案?
创建一个类并自定义 __contains__ 魔术方法,实现则使用 bisect 二分查找。根据这个思路找 GPT 生成了代码:

import bisect

class OrderedList:
def __init__(self, items):
# 确保列表有序
self.items = sorted(items)

# 自定义 in 操作,使用二分查找
def __contains__(self, item):
# 使用 bisect 模块进行二分查找
index = bisect.bisect_left(self.items, item)
# 检查查找到的索引是否在范围内,且对应元素是否与目标相等
return index < len(self.items) and self.items[index] == item

# 支持遍历
def __iter__(self):
return iter(self.items)

# 使用示例
ordered_list = OrderedList([10, 1, 7, 3, 5])

# 遍历
for item in ordered_list:
print(item) # 输出: 1 3 5 7 10 (有序)

# 使用自定义的 in 操作(使用二分查找)
print(7 in ordered_list) # 输出: True
print(6 in ordered_list) # 输出: False
248 天前
回复了 iqoo 创建的主题 程序员 分享一个空间利用率超高的 Base36 算法
原来是分组的 Base36 ,我还以为是常规的 Base36 ,然后优化内存使用
248 天前
回复了 iqoo 创建的主题 程序员 分享一个空间利用率超高的 Base36 算法
我想明白你怎么算的了:

```python
import math

for m in range(1, 16):
n = math.ceil(math.log(256 ** m, 36))
ratio = m / n
print(f'{m=} bytes, {n=} chars, {ratio=:.2%}')
```

m=1 bytes, n=2 chars, ratio=50.00%
m=2 bytes, n=4 chars, ratio=50.00%
m=3 bytes, n=5 chars, ratio=60.00%
m=4 bytes, n=7 chars, ratio=57.14%
m=5 bytes, n=8 chars, ratio=62.50%
m=6 bytes, n=10 chars, ratio=60.00%
m=7 bytes, n=11 chars, ratio=63.64%
m=8 bytes, n=13 chars, ratio=61.54%
m=9 bytes, n=14 chars, ratio=64.29%
m=10 bytes, n=16 chars, ratio=62.50%
m=11 bytes, n=18 chars, ratio=61.11%
m=12 bytes, n=19 chars, ratio=63.16%
m=13 bytes, n=21 chars, ratio=61.90%
m=14 bytes, n=22 chars, ratio=63.64%
m=15 bytes, n=24 chars, ratio=62.50%
248 天前
回复了 iqoo 创建的主题 程序员 分享一个空间利用率超高的 Base36 算法
@iqoo 任何程序编码出来结果都是一样的,和空间利用率有什么关系
248 天前
回复了 iqoo 创建的主题 程序员 分享一个空间利用率超高的 Base36 算法
没有搞懂你说的空间利用率是什么意思。

假设我每次只读取一个字节的数据而不是 9 字节,那么刚开始会产生一个字符,照你的说法空间利用率不就是 100% 了?

每次读取 9 字节只能保证至少产生 13 个字符(比如刚开始时),有时才会产生 14 个字符。当产生 13 个字符时,你说的空间利用率就是 9 / 13 = 0.69 了。

>>> 36 ** 13 < 256 ** 9 < 36 ** 14
True

你可能是想要找到一个缓存区的大小 m ,满足 `256 ** m >= 36 ** n` 且左右两边占用 bits 差值足够小,但是概念上没有理清。我倒是觉得应该这样算:

```python
import math

base = 36

for m in range(1, 16):
n = int(math.log(256 ** m, 36))
waste = m * 8 - math.log2(36 ** n)
print(f'{m=} bytes, {n=} chars, {waste=} bits')
```

m=1 bytes, n=1 chars, waste=2.830074998557688 bits
m=2 bytes, n=3 chars, waste=0.49022499567306355 bits
m=3 bytes, n=4 chars, waste=3.3202999942307514 bits
m=4 bytes, n=6 chars, waste=0.9804499913461271 bits
m=5 bytes, n=7 chars, waste=3.8105249899038114 bits
m=6 bytes, n=9 chars, waste=1.470674987019187 bits
m=7 bytes, n=10 chars, waste=4.3007499855768785 bits
m=8 bytes, n=12 chars, waste=1.9608999826922542 bits
m=9 bytes, n=13 chars, waste=4.790974981249946 bits
m=10 bytes, n=15 chars, waste=2.451124978365314 bits
m=11 bytes, n=17 chars, waste=0.11127497548069698 bits
m=12 bytes, n=18 chars, waste=2.941349974038374 bits
m=13 bytes, n=20 chars, waste=0.601499971153757 bits
m=14 bytes, n=21 chars, waste=3.431574969711434 bits
m=15 bytes, n=23 chars, waste=1.091724966826817 bits

很明显 9 字节的空间利用率非常低。即便如此浪费的也是几个 bits 而已,在字节这个粒度下感觉空间利用率就是个伪需求。
250 天前
回复了 Asuler 创建的主题 程序员 程序员菜鸟,请教一个 web 视频转码的问题
https://chromium.woolyss.com/ 有包含 H.265 解码器的 Chromium 下载
为哈非得在浏览器播放,本地随便起个播放器不行吗
292 天前
回复了 lsk569937453 创建的主题 程序员 有个前端并发数的问题
requestDelay() 前面要加 await 才行,不然你这样就是开启 10 个请求,后面 requestInstant() 只能排队
293 天前
回复了 nbin2008 创建的主题 JavaScript 请教大家一个问题, js,异步执行
你说的这个用 Streams API 很容易实现:

```js
import { setTimeout } from "node:timers/promises";

let id = 0;

async function fetchData() {
console.log("fetchData()");
await setTimeout(Math.random() * 500);
return Array(10)
.fill(null)
.map(() => ({ id: ++id }));
}

const stream = new ReadableStream(
{
async pull(controller) {
const data = await fetchData();
for (const a of data) {
controller.enqueue(a);
}
},
},
new CountQueuingStrategy({ highWaterMark: 0 }), // fully lazy
).values();

async function getD() {
return (await stream.next()).value;
}

for (let i = 0; i < 30; i++) {
console.log("data", await getD());
}
```

输出结果:

fetchData()
data { id: 1 }
data { id: 2 }
data { id: 3 }
data { id: 4 }
data { id: 5 }
data { id: 6 }
data { id: 7 }
data { id: 8 }
data { id: 9 }
data { id: 10 }
fetchData()
data { id: 11 }
data { id: 12 }
data { id: 13 }
data { id: 14 }
data { id: 15 }
data { id: 16 }
data { id: 17 }
data { id: 18 }
data { id: 19 }
data { id: 20 }
fetchData()
data { id: 21 }
data { id: 22 }
data { id: 23 }
data { id: 24 }
data { id: 25 }
data { id: 26 }
data { id: 27 }
data { id: 28 }
data { id: 29 }
data { id: 30 }

这里为了定义你所需要 getD() 显得把代码搞复杂了一点,而实际上你可以根据情况使用 for await... of 。

你从 LLM 获得的代码有轮询的部分,所以强烈不推荐:

await new Promise(resolve => setTimeout(resolve, 100)); // 等待数据获取完成
我用生成器简单实现了一下,代码 https://pastebin.com/hi04KbPd

function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

const then = Date.now();

async function task(name, input) {
console.log(`[${Date.now() - then}] ${name}: start, input: ${input}`);
await delay(1_000);
console.log(`[${Date.now() - then}] ${name}: done, input: ${input}`);
return input + name;
}

let token = 0;
async function onUpdate(input) {
let current = ++token;
async function* gen() {
let result = await task('a', input);
yield;
result = await task('b', result);
yield;
result = await task('c', result);
console.log(`[${Date.now() - then}] final result: ${result}`);
return result;
}
for await (const _ of gen()) {
if (token !== current) break;
}
}

onUpdate('1');
await delay(500);
onUpdate('2');
await delay(2_000);
onUpdate('3');

输出结果:

[0] a: start, input: 1
[510] a: start, input: 2
[1006] a: done, input: 1
[1526] a: done, input: 2
[1527] b: start, input: 2a
[2516] a: start, input: 3
[2533] b: done, input: 2a
[3520] a: done, input: 3
[3520] b: start, input: 3a
[4528] b: done, input: 3a
[4530] c: start, input: 3ab
[5536] c: done, input: 3ab
[5537] final result: 3abc
@rabbbit 用生成器确实是一个好办法,在可被取消的时间点添加 yield 。暂停后只要不调用 next() 方法就不会继续执行,这样就需要自己写一个执行器,调度的时机可以自己灵活控制,像 co 那种执行器是自动执行的,async 函数也是自动执行的。
给输出结果加个时间线:

[1] Starting task A with input: 1
[514] Starting task A with input: 2
[1010] Finishing task A with input: 1
[1537] Finishing task A with input: 2
[1539] Starting task B with input: 2A
[2019] Starting task A with input: 3
[2542] Finishing task B with input: 2A
[3023] Finishing task A with input: 3
[3024] Starting task B with input: 3A
[4027] Finishing task B with input: 3A
[4028] Starting task C with input: 3AB
[5038] Finishing task C with input: 3AB
[5040] Final result: 3ABC
用 RxJS 可以方便地实现,跟 AI 交流了一会后给出了如下代码:

import { Subject, from } from 'rxjs';
import { switchMap } from 'rxjs/operators';

// 模拟异步任务 A 、B 、C
const taskA = (input) => {
console.log(`Starting task A with input: ${input}`);
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Finishing task A with input: ${input}`);
resolve(input + 'A');
}, 1000); // 假设任务 A 需要 1 秒钟
});
};

const taskB = (input) => {
console.log(`Starting task B with input: ${input}`);
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Finishing task B with input: ${input}`);
resolve(input + 'B');
}, 1000); // 假设任务 B 需要 1 秒钟
});
};

const taskC = (input) => {
console.log(`Starting task C with input: ${input}`);
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Finishing task C with input: ${input}`);
resolve(input + 'C');
}, 1000); // 假设任务 C 需要 1 秒钟
});
};

const subject = new Subject();

subject
.pipe(
switchMap((value) =>
from(taskA(value)).pipe(
switchMap((resultA) => from(taskB(resultA))),
switchMap((resultB) => from(taskC(resultB))),
),
),
)
.subscribe((result) => {
console.log('Final result:', result);
});

// 发出一些值
subject.next('1'); // 发出第一个值
setTimeout(() => subject.next('2'), 500); // 在 0.5 秒后发出第二个值,中止第一个任务并开始新的任务
setTimeout(() => subject.next('3'), 2000); // 在 2 秒后发出第三个值

输出结果:

Starting task A with input: 1
Starting task A with input: 2
Finishing task A with input: 1
Finishing task A with input: 2
Starting task B with input: 2A
Starting task A with input: 3
Finishing task B with input: 2A
Finishing task A with input: 3
Starting task B with input: 3A
Finishing task B with input: 3A
Starting task C with input: 3AB
Finishing task C with input: 3AB
Final result: 3ABC

实际上用全套 RxJS API 更简单,无奈 API 忘了好多
1  2  3  4  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   994 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 32ms · UTC 19:18 · PVG 03:18 · LAX 12:18 · JFK 15:18
Developed with CodeLauncher
♥ Do have faith in what you're doing.