Re: [PATCH] mm/slub: serve slabobj_ext array from a strictly larger kmalloc cache
From: Suren Baghdasaryan
Date: Tue Jun 30 2026 - 19:56:03 EST
On Tue, Jun 30, 2026 at 8:27 AM Harry Yoo <harry@xxxxxxxxxx> wrote:
>
>
>
> On 6/30/26 11:52 PM, Suren Baghdasaryan wrote:
> > On Tue, Jun 30, 2026 at 7:36 AM Shakeel Butt <shakeel.butt@xxxxxxxxx> wrote:
> >>
> >> On Tue, Jun 30, 2026 at 04:03:30PM +0900, Harry Yoo wrote:
> >>>
> >>>
> >>> On 6/30/26 3:12 PM, Vlastimil Babka (SUSE) wrote:
> >>>> On 6/30/26 07:29, Suren Baghdasaryan wrote:
> >>>>> On Mon, Jun 29, 2026 at 9:42 PM Harry Yoo <harry@xxxxxxxxxx> wrote:
> >>>>>>
> >>>>>>
> >>>>>>
> >>>>>> On 6/30/26 1:39 PM, Suren Baghdasaryan wrote:
> >>>>>>> On Mon, Jun 29, 2026 at 9:38 PM Suren Baghdasaryan <surenb@xxxxxxxxxx> wrote:
> >>>>>>>>
> >>>>>>
> >>>>>> Ah, here I meant backporting either the kmalloc_flags()+KMALLOC_TYPE or
> >>>>>> SLAB_BUCKETS approach.
> >>>>>>
> >>>>>>>> Yes, it's worth backporting, so we can merge Shakeel's change as is
> >>>>>>
> >>>>>> Right.
> >>>>>>
> >>>>>>>> and then once Vlastimil's patch is merged we can implement the new
> >>>>>>
> >>>>>> Vlastimil's patch has already landed mainline, by the way :)
> >>>>>
> >>>>> Nice! I suggest posting Shakeel's patch CC'ing stable for backports
> >>>>> and then following up with the fix using KMALLOC_TYPE. Vlastimil,
> >>>>> WDYT?
> >>>>
> >>>> Sounded like a plan, but then I realized I misunderstood the amount of the
> >>>> wastage. E.g. on my system kmalloc-8k with 4 objects per slab would have
> >>>> obj_ext size of 64, but now it's 16k? That's ridiculous.
> >>>
> >>> Right.
> >>
> >> Yeah I should have given more thought on wastage.
> >
> > Ugh! I didn't realize the wastage was that high.
>
> Ouch!
>
> >>> ...which is why I was assuming either the KMALLOC_TYPE or SLAB_BUCKETS
> >>> approach would be backported as a follow-up. Err, should have
> >>> communicated clearly, apologies.
> >>
> >> Harry, do you want to take a stab at prototyping these? If these look simple
> >> enough, we can request backports of this.
>
> Ack, let's see what would be the minimal changes to resolve this.
>
> > I'll also give it some thought to see if there is maybe a different
> > way to fix this that would be easy to backport.
>
> Thanks, let's discuss!
So, below patch is based on stable linux-7.1.y branch and I think it's
the simplest backportable way to fix this issue. Build-tested only, so
use just as a prototype. Not sure if making buckets parameter
unconditional from CONFIG_SLAB_BUCKETS has side-effects...
Attaching the patch as well since I'm sure gmail will butcher the tabs.
The fix for 7.2 should be much simpler using a new flag in
kmalloc_flags() for obj_ext vector allocations and serving these from
a dedicated cache.
Please let me know what you think:
---
include/linux/slab.h | 42 +++++++++++++++++++++---------
mm/slub.c | 61 +++++++++++---------------------------------
2 files changed, 45 insertions(+), 58 deletions(-)
diff --git a/include/linux/slab.h b/include/linux/slab.h
index 2b5ab488e96b..2d4a6e3064a3 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -618,6 +618,10 @@ static inline unsigned int arch_slab_minalign(void)
#define RANDOM_KMALLOC_CACHES_NR 0
#endif
+#if defined(CONFIG_MEM_ALLOC_PROFILING) && defined(CONFIG_SLUB_TINY)
+#define OBJ_EXT_VEC_CACHE
+#endif
+
/*
* Whenever changing this, take care of that kmalloc_type() and
* create_kmalloc_caches() still work as intended.
@@ -646,6 +650,9 @@ enum kmalloc_cache_type {
#endif
#ifdef CONFIG_MEMCG
KMALLOC_CGROUP,
+#endif
+#ifdef OBJ_EXT_VEC_CACHE
+ KMALLOC_OBJ_EXT_VEC,
#endif
NR_KMALLOC_TYPES
};
@@ -654,6 +661,18 @@ typedef struct kmem_cache *
kmem_buckets[KMALLOC_SHIFT_HIGH + 1];
extern kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES];
+#ifdef OBJ_EXT_VEC_CACHE
+static inline kmem_buckets *obj_ext_vec_buckets(void)
+{
+ return &kmalloc_caches[KMALLOC_OBJ_EXT_VEC];
+}
+#else
+static inline kmem_buckets *obj_ext_vec_buckets(void)
+{
+ return NULL;
+}
+#endif
+
/*
* Define gfp bits that should not be set for KMALLOC_NORMAL.
*/
@@ -843,20 +862,10 @@ void *kmem_cache_alloc_from_sheaf_noprof(struct
kmem_cache *cachep, gfp_t gfp,
unsigned int kmem_cache_sheaf_size(struct slab_sheaf *sheaf);
-/*
- * These macros allow declaring a kmem_buckets * parameter alongside
size, which
- * can be compiled out with CONFIG_SLAB_BUCKETS=n so that a large
number of call
- * sites don't have to pass NULL.
- */
-#ifdef CONFIG_SLAB_BUCKETS
+/* TODO: these macros should be removed in a prerequisite patch */
#define DECL_BUCKET_PARAMS(_size, _b) size_t (_size), kmem_buckets *(_b)
#define PASS_BUCKET_PARAMS(_size, _b) (_size), (_b)
#define PASS_BUCKET_PARAM(_b) (_b)
-#else
-#define DECL_BUCKET_PARAMS(_size, _b) size_t (_size)
-#define PASS_BUCKET_PARAMS(_size, _b) (_size)
-#define PASS_BUCKET_PARAM(_b) NULL
-#endif
/*
* The following functions are not to be used directly and are intended only
@@ -955,7 +964,13 @@ static __always_inline __alloc_size(1) void
*kmalloc_noprof(size_t size, gfp_t f
}
#define kmalloc(...)
alloc_hooks(kmalloc_noprof(__VA_ARGS__))
-void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node);
+void *__kmalloc_nolock_noprof(size_t size, kmem_buckets *b, gfp_t
gfp_flags, int node);
+#define __kmalloc_nolock(...)
alloc_hooks(__kmalloc_nolock_noprof(__VA_ARGS__))
+
+static __always_inline void *kmalloc_nolock_noprof(size_t size, gfp_t
gfp_flags, int node)
+{
+ return __kmalloc_nolock_noprof(size, NULL, gfp_flags, node);
+}
#define kmalloc_nolock(...)
alloc_hooks(kmalloc_nolock_noprof(__VA_ARGS__))
/**
@@ -1062,6 +1077,9 @@ void *kmalloc_nolock_noprof(size_t size, gfp_t
gfp_flags, int node);
#define kmem_buckets_alloc(_b, _size, _flags) \
alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size,
_b), _flags, NUMA_NO_NODE))
+#define kmem_buckets_node_alloc(_size, _b, _flags, _node) \
+ alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size,
_b), _flags, _node))
+
#define kmem_buckets_alloc_track_caller(_b, _size, _flags) \
alloc_hooks(__kmalloc_node_track_caller_noprof(PASS_BUCKET_PARAMS(_size,
_b), _flags, NUMA_NO_NODE, _RET_IP_))
diff --git a/mm/slub.c b/mm/slub.c
index a2bf3756ca7d..c59d9ee4f4f3 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -2106,42 +2106,6 @@ static inline void init_slab_obj_exts(struct slab *slab)
slab->obj_exts = 0;
}
-/*
- * Calculate the allocation size for slabobj_ext array.
- *
- * When memory allocation profiling is enabled, the obj_exts array
- * could be allocated from the same slab cache it's being allocated for.
- * This would prevent the slab from ever being freed because it would
- * always contain at least one allocated object (its own obj_exts array).
- *
- * To avoid this, increase the allocation size when we detect the array
- * may come from the same cache, forcing it to use a different cache.
- */
-static inline size_t obj_exts_alloc_size(struct kmem_cache *s,
- struct slab *slab, gfp_t gfp)
-{
- size_t sz = sizeof(struct slabobj_ext) * slab->objects;
- struct kmem_cache *obj_exts_cache;
-
- if (sz > KMALLOC_MAX_CACHE_SIZE)
- return sz;
-
- if (!is_kmalloc_normal(s))
- return sz;
-
- obj_exts_cache = kmalloc_slab(sz, NULL, gfp, 0);
- /*
- * We can't simply compare s with obj_exts_cache, because
random kmalloc
- * caches have multiple caches per size, selected by caller address.
- * Since caller address may differ between kmalloc_slab() and actual
- * allocation, bump size when sizes are equal.
- */
- if (s->object_size == obj_exts_cache->object_size)
- return obj_exts_cache->object_size + 1;
-
- return sz;
-}
-
int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
gfp_t gfp, bool new_slab)
{
@@ -2156,19 +2120,23 @@ int alloc_slab_obj_exts(struct slab *slab,
struct kmem_cache *s,
/* Prevent recursive extension vector allocation */
gfp |= __GFP_NO_OBJ_EXT;
- sz = obj_exts_alloc_size(s, slab, gfp);
-
+ sz = sizeof(struct slabobj_ext) * slab->objects;
/*
* Note that allow_spin may be false during early boot and its
* restricted GFP_BOOT_MASK. Due to kmalloc_nolock() only supporting
* architectures with cmpxchg16b, early obj_exts will be missing for
* very early allocations on those.
*/
- if (unlikely(!allow_spin))
- vec = kmalloc_nolock(sz, __GFP_ZERO | __GFP_NO_OBJ_EXT,
- slab_nid(slab));
- else
- vec = kmalloc_node(sz, gfp | __GFP_ZERO, slab_nid(slab));
+ if (unlikely(!allow_spin)) {
+ vec = __kmalloc_nolock(sz, obj_ext_vec_buckets(),
+ __GFP_ZERO | __GFP_NO_OBJ_EXT,
slab_nid(slab));
+ } else {
+ if (sz > KMALLOC_MAX_CACHE_SIZE)
+ vec = kmalloc_node(sz, gfp | __GFP_ZERO,
slab_nid(slab));
+ else
+ vec = kmem_buckets_node_alloc(sz,
obj_ext_vec_buckets(),
+ gfp |
__GFP_ZERO, slab_nid(slab));
+ }
if (!vec) {
/*
@@ -5311,6 +5279,7 @@ EXPORT_SYMBOL(__kmalloc_noprof);
/**
* kmalloc_nolock - Allocate an object of given size from any context.
+ * @b: kmem_cach bucket
* @size: size to allocate
* @gfp_flags: GFP flags. Only __GFP_ACCOUNT, __GFP_ZERO, __GFP_NO_OBJ_EXT
* allowed.
@@ -5320,7 +5289,7 @@ EXPORT_SYMBOL(__kmalloc_noprof);
* NULL does not mean EBUSY or EAGAIN. It means ENOMEM.
* There is no reason to call it again and expect !NULL.
*/
-void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node)
+void *__kmalloc_nolock_noprof(size_t size, kmem_buckets *b, gfp_t
gfp_flags, int node)
{
gfp_t alloc_gfp = __GFP_NOWARN | __GFP_NOMEMALLOC | gfp_flags;
struct kmem_cache *s;
@@ -5347,7 +5316,7 @@ void *kmalloc_nolock_noprof(size_t size, gfp_t
gfp_flags, int node)
retry:
if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
return NULL;
- s = kmalloc_slab(size, NULL, alloc_gfp, _RET_IP_);
+ s = kmalloc_slab(size, b, alloc_gfp, _RET_IP_);
if (!(s->flags & __CMPXCHG_DOUBLE) && !kmem_cache_debug(s))
/*
@@ -5400,7 +5369,7 @@ void *kmalloc_nolock_noprof(size_t size, gfp_t
gfp_flags, int node)
ret = kasan_kmalloc(s, ret, size, alloc_gfp);
return ret;
}
-EXPORT_SYMBOL_GPL(kmalloc_nolock_noprof);
+EXPORT_SYMBOL_GPL(__kmalloc_nolock_noprof);
void *__kmalloc_node_track_caller_noprof(DECL_BUCKET_PARAMS(size, b),
gfp_t flags,
int node, unsigned long caller)
--
>
> >>>> We could say it's for a debugging feature, but also it's running in
> >>>> production fleets (and Android?), so probably not that easy to dismiss.
> >>>
> >>> I think a key factor is when it's enabled in production.
> >>>
> >>> kconfigs says Android selects MEM_ALLOC_PROFILING, but not
> >>> MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT.
> >>>
> >>> I assumed that turning it on by default in the entire fleet
> >>> would be bit hard to justify... (please correct me,
> >>> if it's not the case)
> >>
> >> Actually we have memory profiling enabled by default across Meta fleet. So, the
> >> issue is very real.
>
> I see, thanks for clarifying.
>
> >> At the moment, we are seeing this issue on a specific
> >> type of machine and we have disabled memory profiling for those machines.
> >> Internally we did discuss to simply disable memory allocation profiling for
> >> kmalloc-normal caches but to me that was a big hammer and thus suggested the
> >> current approach.
>
> --
> Cheers,
> Harry / Hyeonggon
From 734bbde845890fd2788e0f8c44e4a79fbfe0c803 Mon Sep 17 00:00:00 2001
From: Suren Baghdasaryan <surenb@google.com>
Date: Tue, 30 Jun 2026 13:04:02 -0700
Subject: [PATCH 1/1] prototype for a fix to the circular allocation issue from
[1]
[1] 20260625230029.703750-1-shakeel.butt@linux.dev/">https://lore.kernel.org/all/20260625230029.703750-1-shakeel.butt@linux.dev/
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
---
include/linux/slab.h | 42 +++++++++++++++++++++---------
mm/slub.c | 61 +++++++++++---------------------------------
2 files changed, 45 insertions(+), 58 deletions(-)
diff --git a/include/linux/slab.h b/include/linux/slab.h
index 2b5ab488e96b..2d4a6e3064a3 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -618,6 +618,10 @@ static inline unsigned int arch_slab_minalign(void)
#define RANDOM_KMALLOC_CACHES_NR 0
#endif
+#if defined(CONFIG_MEM_ALLOC_PROFILING) && defined(CONFIG_SLUB_TINY)
+#define OBJ_EXT_VEC_CACHE
+#endif
+
/*
* Whenever changing this, take care of that kmalloc_type() and
* create_kmalloc_caches() still work as intended.
@@ -646,6 +650,9 @@ enum kmalloc_cache_type {
#endif
#ifdef CONFIG_MEMCG
KMALLOC_CGROUP,
+#endif
+#ifdef OBJ_EXT_VEC_CACHE
+ KMALLOC_OBJ_EXT_VEC,
#endif
NR_KMALLOC_TYPES
};
@@ -654,6 +661,18 @@ typedef struct kmem_cache * kmem_buckets[KMALLOC_SHIFT_HIGH + 1];
extern kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES];
+#ifdef OBJ_EXT_VEC_CACHE
+static inline kmem_buckets *obj_ext_vec_buckets(void)
+{
+ return &kmalloc_caches[KMALLOC_OBJ_EXT_VEC];
+}
+#else
+static inline kmem_buckets *obj_ext_vec_buckets(void)
+{
+ return NULL;
+}
+#endif
+
/*
* Define gfp bits that should not be set for KMALLOC_NORMAL.
*/
@@ -843,20 +862,10 @@ void *kmem_cache_alloc_from_sheaf_noprof(struct kmem_cache *cachep, gfp_t gfp,
unsigned int kmem_cache_sheaf_size(struct slab_sheaf *sheaf);
-/*
- * These macros allow declaring a kmem_buckets * parameter alongside size, which
- * can be compiled out with CONFIG_SLAB_BUCKETS=n so that a large number of call
- * sites don't have to pass NULL.
- */
-#ifdef CONFIG_SLAB_BUCKETS
+/* TODO: these macros should be removed in a prerequisite patch */
#define DECL_BUCKET_PARAMS(_size, _b) size_t (_size), kmem_buckets *(_b)
#define PASS_BUCKET_PARAMS(_size, _b) (_size), (_b)
#define PASS_BUCKET_PARAM(_b) (_b)
-#else
-#define DECL_BUCKET_PARAMS(_size, _b) size_t (_size)
-#define PASS_BUCKET_PARAMS(_size, _b) (_size)
-#define PASS_BUCKET_PARAM(_b) NULL
-#endif
/*
* The following functions are not to be used directly and are intended only
@@ -955,7 +964,13 @@ static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f
}
#define kmalloc(...) alloc_hooks(kmalloc_noprof(__VA_ARGS__))
-void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node);
+void *__kmalloc_nolock_noprof(size_t size, kmem_buckets *b, gfp_t gfp_flags, int node);
+#define __kmalloc_nolock(...) alloc_hooks(__kmalloc_nolock_noprof(__VA_ARGS__))
+
+static __always_inline void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node)
+{
+ return __kmalloc_nolock_noprof(size, NULL, gfp_flags, node);
+}
#define kmalloc_nolock(...) alloc_hooks(kmalloc_nolock_noprof(__VA_ARGS__))
/**
@@ -1062,6 +1077,9 @@ void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node);
#define kmem_buckets_alloc(_b, _size, _flags) \
alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE))
+#define kmem_buckets_node_alloc(_size, _b, _flags, _node) \
+ alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, _node))
+
#define kmem_buckets_alloc_track_caller(_b, _size, _flags) \
alloc_hooks(__kmalloc_node_track_caller_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE, _RET_IP_))
diff --git a/mm/slub.c b/mm/slub.c
index a2bf3756ca7d..c59d9ee4f4f3 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -2106,42 +2106,6 @@ static inline void init_slab_obj_exts(struct slab *slab)
slab->obj_exts = 0;
}
-/*
- * Calculate the allocation size for slabobj_ext array.
- *
- * When memory allocation profiling is enabled, the obj_exts array
- * could be allocated from the same slab cache it's being allocated for.
- * This would prevent the slab from ever being freed because it would
- * always contain at least one allocated object (its own obj_exts array).
- *
- * To avoid this, increase the allocation size when we detect the array
- * may come from the same cache, forcing it to use a different cache.
- */
-static inline size_t obj_exts_alloc_size(struct kmem_cache *s,
- struct slab *slab, gfp_t gfp)
-{
- size_t sz = sizeof(struct slabobj_ext) * slab->objects;
- struct kmem_cache *obj_exts_cache;
-
- if (sz > KMALLOC_MAX_CACHE_SIZE)
- return sz;
-
- if (!is_kmalloc_normal(s))
- return sz;
-
- obj_exts_cache = kmalloc_slab(sz, NULL, gfp, 0);
- /*
- * We can't simply compare s with obj_exts_cache, because random kmalloc
- * caches have multiple caches per size, selected by caller address.
- * Since caller address may differ between kmalloc_slab() and actual
- * allocation, bump size when sizes are equal.
- */
- if (s->object_size == obj_exts_cache->object_size)
- return obj_exts_cache->object_size + 1;
-
- return sz;
-}
-
int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
gfp_t gfp, bool new_slab)
{
@@ -2156,19 +2120,23 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
/* Prevent recursive extension vector allocation */
gfp |= __GFP_NO_OBJ_EXT;
- sz = obj_exts_alloc_size(s, slab, gfp);
-
+ sz = sizeof(struct slabobj_ext) * slab->objects;
/*
* Note that allow_spin may be false during early boot and its
* restricted GFP_BOOT_MASK. Due to kmalloc_nolock() only supporting
* architectures with cmpxchg16b, early obj_exts will be missing for
* very early allocations on those.
*/
- if (unlikely(!allow_spin))
- vec = kmalloc_nolock(sz, __GFP_ZERO | __GFP_NO_OBJ_EXT,
- slab_nid(slab));
- else
- vec = kmalloc_node(sz, gfp | __GFP_ZERO, slab_nid(slab));
+ if (unlikely(!allow_spin)) {
+ vec = __kmalloc_nolock(sz, obj_ext_vec_buckets(),
+ __GFP_ZERO | __GFP_NO_OBJ_EXT, slab_nid(slab));
+ } else {
+ if (sz > KMALLOC_MAX_CACHE_SIZE)
+ vec = kmalloc_node(sz, gfp | __GFP_ZERO, slab_nid(slab));
+ else
+ vec = kmem_buckets_node_alloc(sz, obj_ext_vec_buckets(),
+ gfp | __GFP_ZERO, slab_nid(slab));
+ }
if (!vec) {
/*
@@ -5311,6 +5279,7 @@ EXPORT_SYMBOL(__kmalloc_noprof);
/**
* kmalloc_nolock - Allocate an object of given size from any context.
+ * @b: kmem_cach bucket
* @size: size to allocate
* @gfp_flags: GFP flags. Only __GFP_ACCOUNT, __GFP_ZERO, __GFP_NO_OBJ_EXT
* allowed.
@@ -5320,7 +5289,7 @@ EXPORT_SYMBOL(__kmalloc_noprof);
* NULL does not mean EBUSY or EAGAIN. It means ENOMEM.
* There is no reason to call it again and expect !NULL.
*/
-void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node)
+void *__kmalloc_nolock_noprof(size_t size, kmem_buckets *b, gfp_t gfp_flags, int node)
{
gfp_t alloc_gfp = __GFP_NOWARN | __GFP_NOMEMALLOC | gfp_flags;
struct kmem_cache *s;
@@ -5347,7 +5316,7 @@ void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node)
retry:
if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
return NULL;
- s = kmalloc_slab(size, NULL, alloc_gfp, _RET_IP_);
+ s = kmalloc_slab(size, b, alloc_gfp, _RET_IP_);
if (!(s->flags & __CMPXCHG_DOUBLE) && !kmem_cache_debug(s))
/*
@@ -5400,7 +5369,7 @@ void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node)
ret = kasan_kmalloc(s, ret, size, alloc_gfp);
return ret;
}
-EXPORT_SYMBOL_GPL(kmalloc_nolock_noprof);
+EXPORT_SYMBOL_GPL(__kmalloc_nolock_noprof);
void *__kmalloc_node_track_caller_noprof(DECL_BUCKET_PARAMS(size, b), gfp_t flags,
int node, unsigned long caller)
--
2.55.0.rc0.799.gd6f94ed593-goog