65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML5 Web Workers 简介:JavaScript 多线程方法

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2011 年 10 月 26 日

CPOL

19分钟阅读

viewsIcon

64172

在本文中,我们将看到 HTML5 为 Web 提供了一种更好的方式来处理这些新的、出色的处理器,以帮助您拥抱新一代的 Web 应用程序。

HTML5 应用程序显然是使用 JavaScript 编写的。但与(如原生应用)其他类型的开发环境相比,**JavaScript** 历史上一直存在一个重要限制:**其所有执行过程都只在一个线程中进行**。

这在当今的多核处理器(如拥有多达 8 个逻辑 CPU 的 i5/i7)甚至最新的双核或四核 ARM 移动处理器上都可能非常令人烦恼。幸运的是,我们将看到 HTML5 为 Web 提供了一种更好的方式来处理这些新的、出色的处理器,以帮助您拥抱新一代的 Web 应用程序。

HTML5-Web-Workers/image001.jpg

Worker 出现之前

这种 JavaScript 限制意味着长时间运行的进程将冻结主窗口。我们经常说我们正在阻塞“**UI 线程**”。这是负责处理所有视觉元素和相关任务的主线程:绘制、刷新、动画、用户输入事件等。

我们都知道过度占用该线程的糟糕后果:页面冻结,用户将无法再与您的应用程序进行交互。用户体验当然非常不愉快,用户可能会决定关闭标签页或浏览器实例。这很可能不是您希望发生在您的应用程序上的情况!

为避免这种情况,浏览器实施了保护机制,当发现一个可疑的长运行脚本时会警告用户。

不幸的是,这种机制无法区分一个编写不当的脚本和一个仅仅需要更多时间来完成工作的脚本。尽管如此,因为它会阻塞 UI 线程,所以最好通知您可能发生了一些错误。以下是一些消息示例(来自 Firefox 5 和 IE9)。

HTML5-Web-Workers/image002.jpg

HTML5-Web-Workers/image003.jpg

到目前为止,这些问题很少发生,主要有两个原因:

  1. HTML 和 JavaScript 的使用方式和目标与能够实现多线程任务的其他技术不同。网站为用户提供的体验比原生应用程序要差。
  2. 还有其他方法可以或多或少地解决这个并发问题。

所有 Web 开发者都知道这些方法。例如,我们曾尝试使用 setTimeout()setInterval() 方法来模拟并行任务。由于 XMLHttpRequest 对象避免了在从远程服务器加载资源时冻结 UI,因此也可以异步执行 HTTP 请求。最后,**DOM 事件**让我们能够编写提供多任务同时执行之幻觉的应用程序。幻觉,真的吗?是的!

为了更好地理解原因,让我们来看一段伪代码,看看浏览器内部发生了什么。

<script type="text/javascript">
    function init(){
        { piece of code taking 5ms to be executed } 
        A mouseClickEvent is raised
        { piece of code taking 5ms to be executed }
        setInterval(timerTask,"10");
        { piece of code taking 5ms to be executed }
    }

    function handleMouseClick(){
          piece of code taking 8ms to be executed 
    }

    function timerTask(){
          piece of code taking 2ms to be executed 
    }
</script>

让我们用这段代码在一个模型上进行演示。这个图表在时间尺度上展示了浏览器中发生的情况。

HTML5-Web-Workers/image004.jpg

该图表很好地说明了我们任务的非并行性质。事实上,浏览器只是将各种执行请求排入队列。

  • 从 **0** 到 **5** 毫秒:init() 函数开始一个 5 毫秒的任务。5 毫秒后,用户触发了鼠标单击事件。然而,此时无法处理此事件,因为我们仍在执行 init() 函数,它目前垄断了主线程。单击事件被保存,稍后处理。
  • 从 **5** 到 **10** 毫秒:init() 函数继续处理 5 毫秒,然后要求在 10 毫秒后安排调用 timerTask()。因此,该函数逻辑上应该在 20 毫秒的时间范围内执行。
  • 从 **10** 到 **15** 毫秒:完成 init() 函数的完整运行还需要 5 毫秒。这对应于 15 毫秒的黄色块。由于我们释放了主线程,现在可以开始处理已保存的请求。
  • 从 **15** 到 **23** 毫秒:浏览器首先运行 handleMouseClock() 事件,该事件运行 8 毫秒(蓝色块)。
  • 从 **23** 到 **25** 毫秒:作为副作用,原定在 20 毫秒时间范围内运行的 timerTask() 函数会稍微延迟 3 毫秒。其他计划的帧(30 毫秒、40 毫秒等)得到遵守,因为没有更多代码占用 CPU。

注意:此示例和上述图表(通过功能检测机制以 SVG 或 PNG 格式显示)受到以下文章的启发:HTML5 Web Workers JavaScript 多线程

所有这些技巧并没有真正解决我们最初的问题:所有东西仍然在主 UI 线程中执行。

此外,即使 JavaScript 没有被用于与“高级语言”相同的应用程序类型,随着 HTML5 及其同伴提供的新的可能性,这种情况也开始改变。因此,为 JavaScript 提供一些新功能以使其为构建能够利用并行任务的新一代应用程序做好准备变得更加重要。这正是 Web Workers 的作用。

Web Workers 或如何在 UI 线程之外执行

Web Workers API》定义了一种在后台运行脚本的方法。然后,您可以在主页面之外的线程中执行一些任务,从而不影响绘图性能。但是,就像我们知道并非所有算法都可以并行化一样,并非所有 JavaScript 代码都可以利用 Workers。好的,够了,让我们来看看这些著名的 Workers。

我的第一个 Web Worker

由于 Web Workers 将在单独的线程上执行,因此您需要将它们的代码保存在主页面之外的单独文件中。完成后,您需要实例化一个 Worker 对象来调用它们。

var myHelloWorker = new Worker('helloworkers.js');

您将通过发送第一条消息来启动 worker(从而在 Windows 下启动一个线程)。

myHelloWorker.postMessage();

实际上,Web Workers 和主页面通过消息进行通信。这些消息可以由普通字符串或 JSON 对象组成。为了说明简单的消息传递,我们将首先回顾一个非常基本的示例。它将一个字符串发送给一个 worker,该 worker 将简单地将其与另一个字符串连接起来。为此,请将以下代码添加到“helloworker.js”文件中:

function messageHandler(event) {
    // Accessing to the message data sent by the main page
    var messageSent = event.data;
    // Preparing the message that we will send back
    var messageReturned = "Hello " + messageSent + " from a separate thread!";
    // Posting back the message to the main page
    this.postMessage(messageReturned);
}

// Defining the callback function raised when the main page will call us
this.addEventListener('message', messageHandler, false);

我们刚刚在“helloworkers.js”中定义了一段将在另一个线程上执行的代码。它可以接收来自主页面的消息,对消息执行一些任务,并返回一条消息给您的页面。然后,我们需要编写主页面中的接收器。这是将处理此问题的页面:

<!DOCTYPE html>
<html>
<head>
    <title>Hello Web Workers</title>
</head>
<body>
    <div id="output"></div>

    <script type="text/javascript">
        // Instantiating the Worker
        var myHelloWorker = new Worker('helloworkers.js');
        // Getting ready to handle the message sent back
        // by the worker
        myHelloWorker.addEventListener("message", function (event) {
            document.getElementById("output").textContent = event.data;
        }, false);

        // Starting the worker by sending a first message
        myHelloWorker.postMessage("David");

        // Stopping the worker via the terminate() command
        myHelloWorker.terminate();
    </script>
</body>
</html>

结果将是:“Hello David from a separate thread!” 您很惊讶,不是吗?

请注意,worker 会一直存在,直到您将其关闭。

由于它们不会自动进行垃圾回收,因此由您来控制它们的状态。并且请记住,实例化 worker 会消耗一些内存……而且不要忽视冷启动时间。要停止 worker,有两种可能的解决方案:

  1. 从主调用页面调用 terminate() 命令。
  2. 从 worker 本身通过 close() 命令。

演示:您可以在浏览器中测试这个稍有增强的示例:http://david.blob.core.windows.net/html5/HelloWebWorkers_EN.htm

使用 JSON 发送消息

当然,大多数时候我们将发送更结构化的数据给 Workers。(顺便说一句,Web Workers 也可以通过 Message channels 相互通信。)

但是,将结构化消息发送给 worker 的唯一方法是使用 JSON 格式。幸运的是,目前支持 Web Workers 的浏览器足够好,它们也原生支持 JSON。它们多么好啊!

让我们以前面的代码示例为例。我们将添加一个 WorkerMessage 类型的对象。此类型将用于向我们的 Web Workers 发送带有参数的命令。

让我们使用以下简化的 HelloWebWorkersJSON_EN.htm 网页:

<!DOCTYPE html>
<html>
<head>
    <title>Hello Web Workers</title>
</head>
<body>
    <div id="output"></div>

    <script type="text/javascript">
        // Instantiating the Worker
        var myHelloWorker = new Worker('helloworkers.js');
        // Getting ready to handle the message sent back
        // by the worker
        myHelloWorker.addEventListener("message", function (event) {
            document.getElementById("output").textContent = event.data;
        }, false);

        // Starting the worker by sending a first message
        myHelloWorker.postMessage("David");

        // Stopping the worker via the terminate() command
        myHelloWorker.terminate();
    </script>
</body>
</html>

我们使用的是 不显眼的 JavaScript 方法,这有助于我们将视图与附加的逻辑分开。然后,附加的逻辑存在于 HelloWebWorkersJSON_EN.js 文件中。

// HelloWebWorkersJSON_EN.js associated to HelloWebWorkersJSON_EN.htm

// Our WorkerMessage object will be automatically
// serialized and de-serialized by the native JSON parser
function WorkerMessage(cmd, parameter) {
    this.cmd = cmd;
    this.parameter = parameter;
}

// Output div where the messages sent back by the worker will be displayed
var _output = document.getElementById("output");

/* Checking if Web Workers are supported by the browser */
if (window.Worker) {
    // Getting references to the 3 other HTML elements
    var _btnSubmit = document.getElementById("btnSubmit");
    var _inputForWorker = document.getElementById("inputForWorker");
    var _killWorker = document.getElementById("killWorker");

    // Instantiating the Worker
    var myHelloWorker = new Worker('helloworkersJSON_EN.js');
    // Getting ready to handle the message sent back
    // by the worker
    myHelloWorker.addEventListener("message", function (event) {
        _output.textContent = event.data;
    }, false);

    // Starting the worker by sending it the 'init' command
    myHelloWorker.postMessage(new WorkerMessage('init', null));

    // Adding the OnClick event to the Submit button
    // which will send some messages to the worker
    _btnSubmit.addEventListener("click", function (event) {
        // We're now sending messages via the 'hello' command 
        myHelloWorker.postMessage(new WorkerMessage('hello', _inputForWorker.value));
    }, false);

    // Adding the OnClick event to the Kill button
    // which will stop the worker. It won't be usable anymore after that.
    _killWorker.addEventListener("click", function (event) {
        // Stopping the worker via the terminate() command
        myHelloWorker.terminate();
        _output.textContent = "The worker has been stopped.";
    }, false);
}
else {
    _output.innerHTML = "Web Workers are not supported by your browser. Try with IE10: <a href=\"http://ie.microsoft.com/testdrive\">download the latest IE10 Platform Preview</a>";
}

再说一遍,这个例子非常基础。不过,它应该有助于您理解底层逻辑。例如,没有什么可以阻止您使用相同的方法发送一些将被 AI 或物理引擎处理的游戏元素。

演示:您可以在此处测试此 JSON 示例:http://david.blob.core.windows.net/html5/HelloWebWorkersJSON_EN.htm

浏览器支持

Web Workers 刚刚登陆 IE10 Platform Preview。Firefox(自 3.6 起)、Safari(自 4.0 起)、Chrome 和 Opera 11 也支持它。但是,这些浏览器的移动版本不支持。如果您想获得更详细的支持矩阵,请在此处查看:https://caniuse.cn/#search=worker

为了在代码中动态地知道此功能受支持,请使用**功能检测**机制。(您不应该使用某些用户代理嗅探!)

为了帮助您,有两种可用解决方案。第一个是通过这段非常简单的代码自行测试该功能:

/* Checking if Web Workers are supported by the browser */
if (window.Worker) {
    // Code using the Web Workers
}

第二个是使用著名的 Modernizr 库(现在已在 ASP.NET MVC3 项目模板中原生提供)。然后,只需使用类似这样的代码:

<script type="text/javascript">
    var divWebWorker = document.getElementById("webWorkers");
    if (Modernizr.webworkers) {
        divWebWorker.innerHTML = "Web Workers ARE supported";
    }
    else {
        divWebWorker.innerHTML = "Web Workers ARE NOT supported";
    }
</script>

例如,这是您浏览器当前的支持情况:Web Workers **不支持**在您的浏览器中运行。

这将允许您公开应用程序的两个版本。如果 Web Workers **不支持**,您将像往常一样执行您的 JavaScript 代码。如果 Web Workers **支持**,您将能够将部分 JavaScript 代码推送到 worker,以提高最新浏览器的应用程序性能。这样您就不会破坏任何东西,也不会构建仅适用于最新浏览器的特定版本。它将适用于所有浏览器,只是性能有所不同。

Worker 中不可访问的元素

与其关注您无法从 Worker 访问的内容,不如看看您**只能**访问的内容:

方法 描述
void close(); 终止 worker 线程。
void importScripts(urls); 一个逗号分隔的附加 JavaScript 文件列表。
void postMessage(data); 向 worker 线程发送或从 worker 线程接收消息。

 

属性 类型 描述
location WorkerLocation 表示一个绝对 URL,包括协议、主机、端口、主机名、路径名、搜索和哈希组件。
navigator WorkerNavigator 表示用户代理客户端的身份和 onLine 状态。
self WorkerGlobalScope worker 作用域,包括 WorkerLocation 和 WorkerNavigator 对象。

 

事件 描述
onerror 发生了运行时错误。
onmessage 收到了消息数据。

 

方法 描述
void clearInterval(handle); 取消由 handle 标识的超时。
void clearTimeout(handle); 取消由 handle 标识的超时。
long setInterval(handler, timeout value, arguments); 安排一个超时,在指定的毫秒数后重复运行。请注意,您现在可以直接将其他参数传递给 handler。如果 handler 是 DOMString,它将被编译为 JavaScript。返回一个超时句柄。使用 clearInterval 清除。
long setTimeout(handler, timeout value, arguments); 安排一个超时,在指定的毫秒数后运行。请注意,您现在可以直接将其他参数传递给 handler。如果 handler 是 DOMString,它将被编译为 JavaScript。返回一个超时句柄。使用 clearTimeout 清除。

注意:此表摘自我们的 MSDN 文档:HTML5 Web Worker

总而言之,您**无法访问 DOM**。这里有一张很好的图表总结了这一点:

HTML5-Web-Workers/image007.jpg

例如,由于您无法从 worker 访问 window 对象,因此您将无法访问 Local Storage(无论如何它似乎都不是线程安全的)。这些限制对于习惯于在其他环境中进行多线程操作的开发人员来说可能显得过于严格。然而,最大的好处是我们将不会遇到通常遇到的相同问题:锁定、竞争条件等。使用 Web Workers,我们将不必考虑这些。这使得 Web Workers 非常易于访问,同时在特定场景下允许一些有趣的性能提升。

错误处理与调试

处理从 Web Workers 引发的错误非常容易。您只需订阅 OnError 事件,就像我们对 OnMessage 事件所做的那样。

myWorker.addEventListener("error", function (event) {
    _output.textContent = event.data;
}, false);

这是 Web Workers 原生提供的最好的调试代码的工具……非常有限,不是吗?

F12 开发工具栏,提供更好的调试体验

为了超越这一点,IE10 让您可以在其脚本调试器中直接调试 Web Workers 的代码,就像调试其他脚本一样。

为此,您需要按 F12 键启动开发工具栏,然后导航到“脚本”选项卡。此时您应该看不到与您的 worker 关联的 JS 文件。但是在按下“开始调试”按钮后,它应该会自动显示。

HTML5-Web-Workers/image008-1.jpg

下一步是像调试经典 JavaScript 代码一样调试您的 worker!

HTML5-Web-Workers/image009-1.jpg

IE10 是目前唯一提供此功能的浏览器。如果您想了解有关此功能的更多信息,可以阅读这篇详细文章:IE10 中 Web Workers 的调试

模拟 console.log() 的一个有趣解决方案

最后,您需要知道 console 对象在 worker 中是不可用的。因此,如果您需要通过 .log() 方法跟踪 worker 内部发生的情况,它将不起作用,因为 console 对象未定义。幸运的是,我发现了一个有趣的示例,它通过使用 MessageChannel 来**模拟 console.log()** 行为:Web Workers 的 console.log()。此方法在 IE10、Chrome 和 Opera 中效果很好,但在 Firefox 中不行,因为它尚未支持 MessageChannel

注意:为了使此链接中的示例在 IE10 中正常工作,您需要更改此行代码:

console.log.apply(console,
args); // Pass the args to the real log

为此行:

console.log.apply(console, args); // Pass the args to the real log

然后,您应该能够获得这样的结果:

HTML5-Web-Workers/image010.jpg

演示:如果您想尝试此 console.log() 模拟,请前往此处 -> http://david.blob.core.windows.net/html5/HelloWebWorkersJSONdebug.htm <-

用例和如何识别潜在候选者

Web Workers 适用于哪些场景?

当您浏览网络寻找 Web Workers 的示例用法时,您总会找到相同类型的演示:密集的数学/科学计算。您会找到一些 JavaScript 光线追踪器、分形、素数以及类似的东西。这些演示对于理解 Workers 的工作方式很有用,但对于如何在“真实世界”应用程序中使用它们,我们几乎没有具体的视角。

确实,我们之前在 Web Workers 中看到的资源限制缩小了有趣场景的数量。不过,如果您花一些时间思考,您就会开始看到新的有趣用法:

  • 图像处理,使用从 <canvas><video> 元素提取的数据。您可以将图像分成几个区域,并将它们推送到不同的 Workers 进行并行处理。然后,您将受益于新一代多核 CPU。拥有的越多,速度就越快。
  • 大量数据检索,并且您需要在 XMLHTTPRequest 调用后对其进行解析。如果处理这些数据所需的时间很重要,最好在 Web Worker 中在后台进行处理,以避免冻结 UI 线程。这样您就可以保持应用程序的响应性。
  • 后台文本分析:由于使用 Web Workers 时我们可能拥有更多的 CPU 时间,因此我们可以考虑 JavaScript 中的新场景。例如,我们可以设想在用户输入时实时解析内容,而不会影响 UI 体验。考虑一个应用程序,如 Word(来自我们的 Office Web Apps 套件),利用这种可能性:在后台搜索字典以在用户键入时提供帮助,自动更正等。
  • 并发请求本地数据库。IndexDB 将允许 Local Storage 无法提供的功能:为我们的 Web Workers 提供线程安全的存储环境。

此外,如果您转向视频游戏领域,可以考虑将 AI 或物理引擎推送到 Web Workers。例如,我发现了这个实验:关于 Web Workers、GWT 和新的物理演示,它使用 Box2D 物理引擎与 Workers。对于您的人工智能引擎,这意味着您还可以在相同的时间范围内处理更多数据(例如,在国际象棋游戏中预测更多走法)。

我的一些同事现在可能会争辩说,唯一的限制就是您的想象力!

但总的来说,只要您不需要 DOM,任何可能影响用户体验的耗时 JavaScript 代码都是 Web Workers 的一个好候选者。但是,在使用 Workers 时,您需要注意 3 点:

  1. 初始化时间和与 worker 的通信时间不应超过处理本身。
  2. 使用多个 Worker 的内存成本。
  3. 代码块之间的依赖关系,因为您可能需要一些同步逻辑。我的朋友们,并行化并非易事!

我们这边最近发布了一个名为 Web Workers Fountains 的演示。

HTML5-Web-Workers/image011.jpg

此演示显示了一些粒子效果(喷泉),并为每个喷泉使用 1 个 Web Worker 来尝试以最快的方式计算粒子。然后将每个 Worker 的结果聚合起来显示在 <canvas> 元素中。Web Workers 还可以通过 Message Channels 相互交换消息。在此演示中,它用于要求每个 Worker 何时更改喷泉的颜色。然后,我们通过 Message Channels 循环遍历这个颜色数组:红色、橙色、黄色、绿色、蓝色、紫色和粉色。如果您对细节感兴趣,请查看 Demo3.js 文件中的 LightManager() 函数。

此外,欢迎您在 Internet Explorer 10 中启动此演示,它很有趣!

如何识别代码中的热点

要跟踪瓶颈并识别可以将哪些代码部分发送到 Web Workers,您可以使用 IE9/10 的 F12 工具栏中提供的脚本分析器。它将帮助您识别您的热点。然而,识别一个热点并不意味着您已经识别出了 Web Workers 的一个好候选者。为了更好地理解这一点,让我们一起回顾两个不同的有趣案例。

案例 1:Canvas 中的动画,带速度阅读演示

此演示来自 IE Test Drive,可以直接在此处浏览:Speed Reading。它尝试尽快显示一些字符,使用 <canvas> 元素。目标是给浏览器的硬件加速层实现质量施加压力。但在此之外,是否可以通过将某些操作拆分到线程上来获得更高的性能?我们需要进行一些分析来检查。

如果您在 IE9/10 中运行此演示,您也可以在几秒钟内启动分析器。这是您将获得的此类结果:

HTML5-Web-Workers/image013.jpg

如果您按时间消耗函数递减排序,您会清楚地看到排在前面的函数:DrawLoop()Draw()drawImage()。如果您双击 Draw 行,您将跳转到该方法的代码。然后您会看到几个这样的调用:

surface.drawImage(imgTile, 0, 0, 70, 100, this.left, this.top, this.width, this.height);

其中 surface 对象引用一个 <canvas> 元素。

此简要分析的快速结论是,此演示大部分时间都花费在通过 drawImage() 方法在 Canvas 中进行绘制。由于 Web Worker 无法访问 <canvas> 元素,因此我们无法将这项耗时任务卸载到不同的线程(我们可以想象一些以并发方式处理 <canvas> 元素的方法)。因此,此演示不是 Web Workers 提供的并行化机会的好候选者。

但它很好地说明了您需要实施的过程。如果在一些性能分析后,您发现大部分耗时脚本与 DOM 对象密切相关,那么 Web Workers 将无法帮助您提高 Web 应用程序的性能。

案例 2:Canvas 中的光线追踪器

让我们再举一个简单的例子来理解。让我们来看一个像这样的光线追踪器:Flog.RayTracer Canvas Demo。光线追踪器使用一些 CPU 密集型的数学计算来模拟光线路径。其思想是模拟反射、折射、材质等效果。

让我们在启动脚本分析器时渲染一个场景。您应该会得到类似这样的结果:

HTML5-Web-Workers/image014.jpg

同样,如果我们按时间消耗递减排序函数,有两个函数似乎占用了大部分时间:renderScene()getPixelColor()

getPixelColor() 方法的目的是计算当前像素。事实上,光线追踪是逐像素渲染场景的。然后,此 getPixelColor() 方法调用负责渲染阴影、环境光等的 rayTrace() 方法。这是我们应用程序的核心。如果您查看 rayTrace() 函数的代码,您会发现它是 100% 纯 JavaScript。此代码没有 DOM 依赖。好吧,我认为您会明白:此示例是并行化的一个非常好的候选者。此外,由于每个像素的计算之间不需要同步(此演示中未使用抗锯齿),因此我们可以轻松地将图像渲染拆分到多个线程(因此可能拆分到多个 CPU)。

因此,我们发现一些使用 Web Workers 的光线追踪器示例也就不足为奇了,例如:http://nerget.com/rayjs-mt/rayjs.html

在使用 IE10 分析此光线追踪器后,我们可以看到不使用 Worker 和使用 4 个 Worker 之间的重要差异:

HTML5-Web-Workers/image015.jpg

在第一个屏幕截图中,processRenderCommand() 方法使用了几乎所有可用的 CPU,场景渲染时间为 **2.854 秒**。

使用 4 个 Web Workers,processRenderCommand() 方法在 4 个不同的线程上并行执行。我们甚至可以在右侧列中看到它们的 Worker ID。这次场景渲染时间为 **1.473 秒**。效果是真实的:场景的渲染速度快了 2 倍。

结论

在审查/架构 JavaScript 代码以实现并行执行方面,Web Workers 并没有什么神奇或新的概念。您需要隔离代码中耗时部分。它需要与页面其余逻辑相对独立,以避免等待同步任务。最重要的一点:代码不应与 DOM 相关联。如果满足所有这些条件,请考虑 Web Workers。它们绝对可以帮助您提高 Web 应用程序的整体性能!

额外资源

这里有一些有趣的附加资源可供阅读:

© . All rights reserved.